Question for a REAL expert on casting double to float...

B

Bill Reid

I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double". (This never cropped up before, since
I rarely use "float"s for anything, and hardly ever use the function for
floating-point numbers in the first place; I just was messing around
testing it for all cases and noticed a problem.)

Anyway, it is declared and I assume largely defined the same
way sscanf() is:

int scan_line(char *line,char *format,...) {
va_list var_argument;

/* loop to match up format string with argument list and assign to
pointers */
}

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

case 'f' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,double *)=strtod(number_buf,NULL);
break;

case 's' :
copy_max_length_text
(va_arg(var_argument,char *),field,field_length);
break;

....

"case 'f'" is assigned as 0.0000... if the argument actually points
to a float, but works OK assigned to a double. There may be something
simple I'm missing here, but I get confused enough working with
variable argument list, because of the default promotions of var_arg(),
but I'm not sure that is even the issue here...

I'm assuming it must be possible to do this right, since sscanf()
does it right, but like I say I'm probably missing something very
simple...
 
B

Ben Bacarisse

Bill Reid said:
I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double".
Anyway, it is declared and I assume largely defined the same
way sscanf() is:

int scan_line(char *line,char *format,...) {
va_list var_argument;

/* loop to match up format string with argument list and assign to
pointers */
}

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

I'd advice against this. Most people will expect 'd' to be for int
not unsigned long. You will get the same problem you now see with
float if this code gets to a system where ints and longs are different
sizes.
case 'f' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,double *)=strtod(number_buf,NULL);
break;
...

"case 'f'" is assigned as 0.0000... if the argument actually points
to a float, but works OK assigned to a double. There may be something
simple I'm missing here, but I get confused enough working with
variable argument list, because of the default promotions of var_arg(),
but I'm not sure that is even the issue here...

The issue is that you can't "lie" using va_arg. If the pointer is
pointer to a float, you must convert the argument to a float pointer.
pretending, as your code does, that it points to something that is
probably of a different size will just not work.

It is the same as writing:

float f;
double *dp = (double *)&f;
*dp = 42;
I'm assuming it must be possible to do this right, since sscanf()
does it right, but like I say I'm probably missing something very
simple...

Scanf has "length modifiers" so %f expects a float *, but %lf expects
a double * (%Lf is for long double *).
 
R

Raymond Martineau

I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double". (This never cropped up before, since
I rarely use "float"s for anything, and hardly ever use the function for
floating-point numbers in the first place; I just was messing around
testing it for all cases and noticed a problem.)
[...]

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

Scanf actually treats "%d" as a signed integer. If you want to use a
long, you probably want to have %ld instead.
case 'f' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,double *)=strtod(number_buf,NULL);
break;

In this case, sscanf uses %f for a float rather than a double. If you
want it to work exactly like scanf, change the double* to float*, and
add handling for %lf.

In general, you need to add a case for 'l' and set a flag indicating
that you are dealing with a long/double instead of an int/float. If
you don't have the handling for this flag, you won't get the expected
result.
 
B

Bill Reid

OK, I've read all the responses so far (three), I guess I've got it figured
out...I still have an outstanding question/confusion, though...

Raymond Martineau said:
I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double". (This never cropped up before, since
I rarely use "float"s for anything, and hardly ever use the function for
floating-point numbers in the first place; I just was messing around
testing it for all cases and noticed a problem.)
[...]

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

Scanf actually treats "%d" as a signed integer. If you want to use a
long, you probably want to have %ld instead.

Except it hardly matters in REAL *scanf() functions what you use
as a specifier for integer numbers, but I haven't checked my "fake"
scan_line() for this case (I just use this to assign to an "unsigned"
which has the same sizeof() as "unsigned long" on my system).

This is probably the heart of my confusion; you can kind of do
anything with integers, but floating-point numbers are "touchier"...I
have a long history of completely abusing the format specifiers
for integers (I rarely if ever get them right) in *scanf() functions
without noticeable problems, so when wrote my own scan_line()
I blew off full checking of the complete format specifier for "floats"
as well...
In this case, sscanf uses %f for a float rather than a double. If you
want it to work exactly like scanf, change the double* to float*, and
add handling for %lf.

Yeah, that works...and of course there is no other way I can see
to get it to work, and that's EXACTLY how *scanf() functions work,
so that's the answer...
In general, you need to add a case for 'l' and set a flag indicating
that you are dealing with a long/double instead of an int/float. If
you don't have the handling for this flag, you won't get the expected
result.

OK, I have spent a few minutes this afternoon re-writing the
loop to check the full format specifier (or at least my still abbreviated
version of the format specifiers) for the correct action, so it works
EXACTLY as you've said.

My REAL question NOW is: this must have something to do
with fundamental promotions/assignments, not anything to do
with *scanf() or my "fake" scan_line() per se, right? Why else
the big difference between integer types and float types? I was
also confused about exactly how the "type" argument for
va_arg() worked, I thought it HAD to be the EXACT same
type as the return value of the conversion function (strtod()).
 
I

Ian Collins

Bill said:
My REAL question NOW is: this must have something to do
with fundamental promotions/assignments, not anything to do
with *scanf() or my "fake" scan_line() per se, right? Why else
the big difference between integer types and float types? I was
also confused about exactly how the "type" argument for
va_arg() worked, I thought it HAD to be the EXACT same
type as the return value of the conversion function (strtod()).
It's not a case of integer types behaving differently from floating
point types, it's a case of (on your system) int and long being the same
size where float and double differ.

If you'd been using an ILP64 (43 bit int, 64 bit long) system you would
have had problems with integer types as well.

This should be "-- "
 
B

Ben Bacarisse

Bill Reid said:
OK, I've read all the responses so far (three), I guess I've got it figured
out...I still have an outstanding question/confusion, though...

Raymond Martineau said:
I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double". (This never cropped up before, since
I rarely use "float"s for anything, and hardly ever use the function for
floating-point numbers in the first place; I just was messing around
testing it for all cases and noticed a problem.)
[...]

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

Scanf actually treats "%d" as a signed integer. If you want to use a
long, you probably want to have %ld instead.

Except it hardly matters in REAL *scanf() functions what you use
as a specifier for integer numbers, but I haven't checked my "fake"
scan_line() for this case (I just use this to assign to an "unsigned"
which has the same sizeof() as "unsigned long" on my system).

This will lead you astray. It is just as wrong with integer types.
It works because on your current implementation the sizes and alignment
requirement of int and long and the same. Try using your scanf to
read into a short using %d.
This is probably the heart of my confusion; you can kind of do
anything with integers, but floating-point numbers are "touchier"...

Well it looks like but not quite how you see it. You can't do
anything with ints either. You just got away with whatever you've
tried so far.
I
have a long history of completely abusing the format specifiers
for integers (I rarely if ever get them right) in *scanf() functions
without noticeable problems, so when wrote my own scan_line()
I blew off full checking of the complete format specifier for "floats"
as well...

You did not try very hard. It is simple to get garbage from scanf by
supplying junk arguments. Some systems give less baffling results
(endianness kicks in a can make it look like things worked) but you
can get rubbish from scanf on any system.
Yeah, that works...and of course there is no other way I can see
to get it to work, and that's EXACTLY how *scanf() functions work,
so that's the answer...

It is the only way to go.
OK, I have spent a few minutes this afternoon re-writing the
loop to check the full format specifier (or at least my still abbreviated
version of the format specifiers) for the correct action, so it works
EXACTLY as you've said.

My REAL question NOW is: this must have something to do
with fundamental promotions/assignments, not anything to do
with *scanf() or my "fake" scan_line() per se, right? Why else
the big difference between integer types and float types?

There is not difference. The code is as wrong for ints and for
floats. The undefined behaviour happens to give rise to what you
expect on the implementations you've tried. Your were unlucky.

Try %d with a short *. This is probably closer to the float/double
issue, but the bottom line is the type of pointer you turn the va_arg
into must be compatible with the argument the user passed.
I was
also confused about exactly how the "type" argument for
va_arg() worked, I thought it HAD to be the EXACT same
type as the return value of the conversion function (strtod()).

Eh? One is a pointer the other is not. If you mean the deferenced
pointer to must match what strtod() returns, then that is much less
important than the problem we've been discussing. C happily converts
values between many types, so:

*va_arg(var_argument, double *) = strtoul(number_buf, NULL, 10);

is fine if you want to implement some odd sort of "whole number"
floating point input format.
 
B

Bill Reid

Ben Bacarisse said:
Bill Reid said:
OK, I've read all the responses so far (three), I guess I've got it figured
out...I still have an outstanding question/confusion, though...

Raymond Martineau said:
On Sat, 12 Jul 2008 21:17:54 GMT, "Bill Reid"

I just noticed that my "improved" version of sscanf() doesn't assign
floating point numbers properly if the variable assigned to is declared
as a "float" rather than a "double". (This never cropped up before, since
I rarely use "float"s for anything, and hardly ever use the function for
floating-point numbers in the first place; I just was messing around
testing it for all cases and noticed a problem.)

[...]

The problem is when I get down to the point of actually assigning
values to the pointers:

switch(*format_buf) {

case 'd' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,unsigned long *)=
strtoul(number_buf,NULL,10);
break;

Scanf actually treats "%d" as a signed integer. If you want to use a
long, you probably want to have %ld instead.

Except it hardly matters in REAL *scanf() functions what you use
as a specifier for integer numbers, but I haven't checked my "fake"
scan_line() for this case (I just use this to assign to an "unsigned"
which has the same sizeof() as "unsigned long" on my system).

This will lead you astray. It is just as wrong with integer types.
It works because on your current implementation the sizes and alignment
requirement of int and long and the same. Try using your scanf to
read into a short using %d.

No thanks, I'll take your word for it (or maybe I WILL try it). In that
case, there is NO equivalent *scanf() specifier, right? So are you saying
it is impossible to *scanf() into a short? If it is, how do you do it?
Well it looks like but not quite how you see it. You can't do
anything with ints either. You just got away with whatever you've
tried so far.

They call me "Lucky" down at the club for multiple lottery winners...
You did not try very hard. It is simple to get garbage from scanf by
supplying junk arguments. Some systems give less baffling results
(endianness kicks in a can make it look like things worked) but you
can get rubbish from scanf on any system.

Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!! Why do you think I "rolled my own"? (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)
It is the only way to go.

In this case, yes. But what about unsigned/signed integers, or
decimal vs. integer vs. properly-formated octal and hexadecimal
numbers? Isn't stroul() going to properly convert all positive values,
and they can all be assigned properly to any properly-sized integer
variable, save the possible overflow from assigning a large unsigned
to a signed, and vice-versa?
There is not difference. The code is as wrong for ints and for
floats. The undefined behaviour happens to give rise to what you
expect on the implementations you've tried. Your were unlucky.

That's not what they say at the club...
Try %d with a short *. This is probably closer to the float/double
issue, but the bottom line is the type of pointer you turn the va_arg
into must be compatible with the argument the user passed.

OK, I'm being a little more careful; remember in the following, I
don't HAVE to use ALL the EXACT *scanf() specifers, just my own
simpler "work-alikes":

if(*format_buf!='s')
copy_max_length_text(number_buf,field,field_length);

switch(*format_buf) {

case 'd' :
*va_arg(var_argument,int *)=
strtol(number_buf,NULL,10);
break;

case 'D' :
*va_arg(var_argument,long int *)=
strtol(number_buf,NULL,10);
break;

case 'i' :
*va_arg(var_argument,int *)=
strtol(number_buf,NULL,0);
break;

case 'I' :
*va_arg(var_argument,long int *)=
strtol(number_buf,NULL,0);
break;

case 'u' :
*va_arg(var_argument,unsigned *)=
strtoul(number_buf,NULL,10);
break;

case 'U' :
*va_arg(var_argument,long unsigned *)=
strtoul(number_buf,NULL,10);
break;

case 'f' :
copy_max_length_text(number_buf,field,field_length);
*va_arg(var_argument,float *)=strtod(number_buf,NULL);
break;

case 'F' :
*va_arg(var_argument,double *)=
strtod(number_buf,NULL);
break;

case 's' :
copy_max_length_text
(va_arg(var_argument,char *),field,field_length);
break;

That MORE than covers everything I use the function for now...
Eh? One is a pointer the other is not. If you mean the deferenced
pointer to must match what strtod() returns, then that is much less
important than the problem we've been discussing. C happily converts
values between many types, so:

*va_arg(var_argument, double *) = strtoul(number_buf, NULL, 10);

is fine if you want to implement some odd sort of "whole number"
floating point input format.

Well, as a matter of fact, lacking a true "long unsigned" on this
system, I regularly use "doubles" as decimal "integer" work-alikes
giving me much bigger "integers"...
 
S

santosh

Bill Reid wrote:

No thanks, I'll take your word for it (or maybe I WILL try it). In
that case, there is NO equivalent *scanf() specifier, right? So are
you saying it is impossible to *scanf() into a short? If it is, how
do you do it?

I haven't followed this thread so I'm probably missing something, but
what's wrong with using the %hd (and variants like %hu %hx %ho)
specifier to scan a short?
 
S

Serve Lau

Bill Reid said:
No thanks, I'll take your word for it (or maybe I WILL try it). In that
case, there is NO equivalent *scanf() specifier, right? So are you saying
it is impossible to *scanf() into a short? If it is, how do you do it?

On 32-bit windows with MSVC:

short x;
scanf("%d", &x);

"Run-time check failure - Stack around the variable 'x' was corrupted"

short x;
scanf("%hd", &x);

No problem
 
B

Ben Bacarisse

Bill Reid did not write that (I did). That there is a missing
attribution can be deduced by counting >>s but it is much nicer you
to leave the attribution for the text you quote.
 
B

Ben Bacarisse

Bill Reid said:
No thanks, I'll take your word for it (or maybe I WILL try it). In that
case, there is NO equivalent *scanf() specifier, right? So are you saying
it is impossible to *scanf() into a short? If it is, how do you do
it?

Don't you have a C library manual? It is all documented. Most
systems also have extensions (and some have limitations) so you can't
avoid reading your library's documentation.
They call me "Lucky" down at the club for multiple lottery
winners...

But in fact this is *unlucky* (as I said below). Lucky programmers
get to see their bugs early.

<snip>
 
B

Bill Reid

Ben Bacarisse said:
Don't you have a C library manual? It is all documented. Most
systems also have extensions (and some have limitations) so you can't
avoid reading your library's documentation.

No, I can't avoid READING it (and the hallowed C "standard"), but
I absolutely can't MEMORIZE it, particularly when the mnemonics
are as counter-intuitive as using "h" to identify a "short" (of course
to avoid "letter-space" conflicts with the "string" specifier "s").

And of course, virtually NO "C" documentation explains the
ACTUAL ramifications of mismatched arguments and format
specifiers in clear simple language. My compiler documentation
just lists the argument "modifiers" as "optional", meaning of
course, to the "writers" of the documentation, that they aren't a
required part of the legal SYNTAX of a format specifier. Of
course, to a "normal" human being looking for information as
quickly as possible, it means you can safely ignore them...

Actually, it doesn't seem that "hh" is part of MY library's
*scanf() argument modifiers, so I may try that to see what
happens (it's possible it just isn't documented)...
But in fact this is *unlucky* (as I said below). Lucky programmers
get to see their bugs early.

I guess I was lucky that I saw all those precision errors that came
from using the completely useless "float" type, so I never used it
ever again...
 
S

santosh

Bill said:
No, I can't avoid READING it (and the hallowed C "standard"),

He was talking about the documentation that comes with your standard
library, not the Standard.
but I absolutely can't MEMORIZE it, particularly when the mnemonics
are as counter-intuitive as using "h" to identify a "short" (of course
to avoid "letter-space" conflicts with the "string" specifier "s").

And of course, virtually NO "C" documentation explains the
ACTUAL ramifications of mismatched arguments and format
specifiers in clear simple language. My compiler documentation
just lists the argument "modifiers" as "optional", meaning of
course, to the "writers" of the documentation, that they aren't a
required part of the legal SYNTAX of a format specifier. Of
course, to a "normal" human being looking for information as
quickly as possible, it means you can safely ignore them...

Well my C library documentation unambiguously states that the 'h'
modifier applies to short and 'l' to long and 'hh' to char and 'z' to
size_t and 'p' to void* and so on. Cant see what more one needs.
Actually, it doesn't seem that "hh" is part of MY library's
*scanf() argument modifiers, so I may try that to see what
happens (it's possible it just isn't documented)...

'hh' is new in C99. 'h' OTOH is part of C90. Similarly 'z' and 'j' are
also new to C99, as is 'm'.

<snip>
 
K

Keith Thompson

Ian Collins said:
It's not a case of integer types behaving differently from floating
point types, it's a case of (on your system) int and long being the same
size where float and double differ.

If you'd been using an ILP64 (43 bit int, 64 bit long) system you would
have had problems with integer types as well.

It's also possible that reading into a long object with "%d" will
happen to "work" even if long is wider than int. If *scanf writes a
int value into the low-order half of a long object, and if the
high-order half of that object happens to have contained zero bits, it
could easily appear to work (and fail badly next time, after some
seemingly irrelevant change).

Which just emphasizes the point that you, the programmer, need to get
this right. Unfortunately, it's an area where you can't count on much
help from the compiler in diagnosing any errors.

This is an almost inevitable result of the design of *printf and
*scanf. Unlike other functions, which specify the types of their
parameters in their declarations, so the compiler can check them, they
specify the expected types of their arguments in a format string.
It's very convenient, and almost elegant, when you get it right, but
it goes badly awry if you (the programmer) get it wrong.

The alternative, in the absence of something like function or operator
overloading, would have been a plethora of individual functions with
distinct names for different types:

print_string("x = ");
print_int(x, 0); /* 0 is the width; more arguments might be needed */
print_char('\n');

which would have been unwieldy, but would have allowed compile-time
type checking.
 
K

Keith Thompson

Bill Reid said:
Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!! Why do you think I "rolled my own"? (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)
[...]

Are you sure *scanf() can't handle your requirements? You didn't know
about "%hd"; perhaps there are other features you don't know about
that would solve your problem.
 
B

Bill Reid

Keith Thompson said:
Bill Reid said:
Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!! Why do you think I "rolled my own"? (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)
[...]

Are you sure *scanf() can't handle your requirements? You didn't know
about "%hd"; perhaps there are other features you don't know about
that would solve your problem.

I sure hope not, but I'll take you up on this. The most important
thing I need for a certain amount of my file parsing is that the
*scan*() function ALWAYS increments to the next argument whether
or not the last field was actually successfully scanned. I also prefer
more of a "regular expression" type of match-up between text that
is or is not a scannable field; the next non-scannable text is NOT
delimited by a mere failure to convert, but the appearance of text
matching the next not-to-be-scanned sequence of characters.

The first absolute requirement is basically so I can be sure that
I correctly scan in empty strings (""), or any strings at all, and any
existing numbers, after the function has failed to convert one or more
scan fields; if it fails to scan fields for arguments 1, 2, and 3, I still
want it to assign arguments 4, 5, 6 correctly, from the correct
fields according to the "regular expression".

If *scanf() can actually do that, I've wasted some time, but it
would completely contradict my experience with what everybody
acknowledges are some quirky and downright dangerous functions,
and how they are explicitly documented...
 
K

Keith Thompson

Bill Reid said:
Keith Thompson said:
Bill Reid said:
Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!! Why do you think I "rolled my own"? (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)
[...]

Are you sure *scanf() can't handle your requirements? You didn't know
about "%hd"; perhaps there are other features you don't know about
that would solve your problem.

I sure hope not, but I'll take you up on this. The most important
thing I need for a certain amount of my file parsing is that the
*scan*() function ALWAYS increments to the next argument whether
or not the last field was actually successfully scanned.

I'm not quite sure what that means. If an argument isn't successfully
scanned, how do you even define how much text to skip to get to the
next argument?

In other words, what does a "field" look like?

You might want to handle this in two steps. First, parse the input
line into fields (perhaps delimited by white space). Then analyze
each field, perhaps using the strdo*() functions.
I also prefer
more of a "regular expression" type of match-up between text that
is or is not a scannable field; the next non-scannable text is NOT
delimited by a mere failure to convert, but the appearance of text
matching the next not-to-be-scanned sequence of characters.

There are open-source regular expression packages out there. Perhaps
using one of them would be easier than rolling your own.
The first absolute requirement is basically so I can be sure that
I correctly scan in empty strings (""), or any strings at all, and any
existing numbers, after the function has failed to convert one or more
scan fields; if it fails to scan fields for arguments 1, 2, and 3, I still
want it to assign arguments 4, 5, 6 correctly, from the correct
fields according to the "regular expression".

That's an odd requirement. Typically if I'm reading and parsing a
line of text, and there's a syntax error (scanning failure) at the
beginning, I just reject the whole line. But if those are your
requirements, that's ok.
If *scanf() can actually do that, I've wasted some time, but it
would completely contradict my experience with what everybody
acknowledges are some quirky and downright dangerous functions,
and how they are explicitly documented...

You may well be right, but I'm still don't completely understand your
requirements. A concrete example or two might help.
 
B

Bill Reid

Keith Thompson said:
Bill Reid said:
Keith Thompson said:
[...]
Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!! Why do you think I "rolled my own"? (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)
[...]

Are you sure *scanf() can't handle your requirements? You didn't know
about "%hd"; perhaps there are other features you don't know about
that would solve your problem.

I sure hope not, but I'll take you up on this. The most important
thing I need for a certain amount of my file parsing is that the
*scan*() function ALWAYS increments to the next argument whether
or not the last field was actually successfully scanned.

I'm not quite sure what that means. If an argument isn't successfully
scanned, how do you even define how much text to skip to get to the
next argument?

Man, have you got a standard library "mindset"...can't comprehend
anything later than 1975 and punch-cards...

Of course, I explain it below, but you still don't seem to be able
to "get it"...
In other words, what does a "field" look like?

"don't scan%d2nd don't scan%ddon't scan%fdon't scan%s"

See the three fields to be scanned? Here, here's some precious
"whitespace" to help:

"don't scan %d 2nd don't scan %d don't scan %f don't scan %s"
You might want to handle this in two steps. First, parse the input
line into fields (perhaps delimited by white space). Then analyze
each field, perhaps using the strdo*() functions.

Are you missing the fact that I already did this years ago? Did the
fact that I posted the code for it throw you off? The scannable fields
are not delimited by anything other than the preceding (if any) and
following text, which I then use to extract a pointer to the
beginning of the scannable field and the length the field. Numeric
fields are copied to a buffer for strto*() function conversion and
assigned to their pointer arguments; string fields are
directly copied to their pointer arguments.
There are open-source regular expression packages out there. Perhaps
using one of them would be easier than rolling your own.

Maybe I already did that too, though this is more like "regular
expression"-lite...hell, a full "regex" package came with my
compiler...stuff
like that is literally "a dime a dozen"...
That's an odd requirement. Typically if I'm reading and parsing a
line of text, and there's a syntax error (scanning failure) at the
beginning, I just reject the whole line. But if those are your
requirements, that's ok.

Actually, it's not that odd, if you could change your "mindset".
I think the way *scanf() works is pretty odd; it's kind of like fishing
while blind in a sewer, mostly you just catch CHUDs...

I don't use this to "fish" for anything; I know exactly what I want,
mostly because I wrote it there. Sometimes I don't want anything, so
that's what I write...and that's what I want when I read it back...
You may well be right, but I'm still don't completely understand your
requirements. A concrete example or two might help.

Remember our format string:

"don't scan %d 2nd don't scan %d don't scan %f don't scan %s"

OK, here are some strings and how they SHOULD scan:

"don't scan 123 2nd don't scan 345 don't scan 1.23 don't scan scan"
field 1 = 123, field 2 = 345, field 3 = 1.23, field 4 = "scan"

"don't scan 2nd don't scan don't scan 1.23 don't scan "
field 1 = who cares, field 2 = who cares, field 3 = 1.23, field 4 = ""

"don't scan 2nd don't scan don't scan don't scan scan"
field 1 = who cares, field 2 = who cares, field 3 = who cares, field 4 =
"scan"

This is what I want, and what I GET, with my version of *scanf(), but
the idiotic *scanf() functions would probably put 2 into field 1
for the second two, and 1 into field 2 for the third, which isn't so
bad because I didn't care about those fields in the first place,
but write garbage into field 3 for the second, if not just crashing
completely, and definitely bomb out on field 4 for the second
two, because they would have been scanning in vain for
some digits for strtol() or strtod() to convert for field 2 or 3...as I
explained very simply, they don't advance automatically to the
next argument if a field fails to scan, instead they just look for
something to scan into the current argument, quite often with
program-ending results...
 
N

Nick Keighley

It's also possible that reading into a long object with "%d" will
happen to "work" even if long is wider than int.  If *scanf writes a
int value into the low-order half of a long object, and if the
high-order half of that object happens to have contained zero bits, it
could easily appear to work (and fail badly next time, after some
seemingly irrelevant change).

Which just emphasizes the point that you, the programmer, need to get
this right.  Unfortunately, it's an area where you can't count on much
help from the compiler in diagnosing any errors.

This is an almost inevitable result of the design of *printf and
*scanf.  Unlike other functions, which specify the types of their
parameters in their declarations, so the compiler can check them, they
specify the expected types of their arguments in a format string.
It's very convenient, and almost elegant, when you get it right, but
it goes badly awry if you (the programmer) get it wrong.

actually some compilers *can* diagnose (some) problems with
printf() and scanf(). For instance gcc. And I believe you can
"mark" functions so the same diagnostics are done on your
own functions. Of course dynamically constructed format
strings blow it away.
The alternative, in the absence of something like function or operator
overloading, would have been a plethora of individual functions with
distinct names for different types:

    print_string("x = ");
    print_int(x, 0); /* 0 is the width; more arguments might be needed */
    print_char('\n');

which would have been unwieldy, but would have allowed compile-time
type checking.

in C++ I'm frequently tempted to fall back on good old
sprintf() it seems so much easier.

// print to two decimal places
cout << fixed << setprecision(decimal_places) << x;

/* print to two decimal places */
printf ("%.2f", x);
 
N

Nick Keighley

please leave attributions at the top. I've moved some


that's because you can't do that

void read_stuff()
{
float f;
double ff;
scanf ("%f %lf", f, ff);
}

No thanks, I'll take your word for it (or maybe I WILL try it).  In that
case, there is NO equivalent *scanf() specifier, right?  So are you saying
it is impossible to *scanf() into a short?  If it is, how do you do it?

"%hd"

might it be an idea to read the scanf() spec? K&R has reasonable
summary in section B1.3.
They call me "Lucky" down at the club for multiple lottery winners...



Hey, "rubbish" would be a treat compared to what often happens
with *scanf()!!!  Why do you think I "rolled my own"?  (I actually have
some very specific requirements that can't be handled properly by
*scanf() at all.)

what requirements? Maybe it would be better to wrapper scanf()
that way you can extend it without reinventing the wheel.

case 'q':
handle_q_format();
break;
case 'v':
handle_v_format();
break;
default:
scanf();

if it wasn't one of your special formats then call scanf()
In this case, yes.  But what about unsigned/signed integers,

use the %u format

or
decimal vs. integer vs.

??

decimal *is* integer. Or rather decimal is a representation
of integer.
properly-formated octal and hexadecima numbers?

%o and %x

 Isn't stroul() going to properly convert all positive values,
and they can all be assigned properly to any properly-sized integer
variable, save the possible overflow from assigning a large unsigned
to a signed, and vice-versa?

what? How did we shift from scanf to strtoul()? Are you saying
strtoul() does what you want or not?... I'm confused.

That's not what they say at the club...


OK, I'm being a little more careful; remember in the following, I
don't HAVE to use ALL the EXACT *scanf() specifers, just my own
simpler "work-alikes":

but *why*?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,905
Latest member
Kristy_Poole

Latest Threads

Top