How to sscanf return integer only

Y

Yogi_Bear_79

I have the following code:

sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;

It only partially works. If the user types a character other than 0-9 to
start the string it fails. However as long as the first character is an
integer it will allow letters in the following places

for Example:

H78 (fails as expected)
78 (works as expected)
7H8 ( doesn't fail as desired)
 
J

Jordan Abel

I have the following code:

sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;

It only partially works. If the user types a character other than 0-9 to
start the string it fails. However as long as the first character is an
integer it will allow letters in the following places

for Example:

H78 (fails as expected)
78 (works as expected)
7H8 ( doesn't fail as desired)

But there _is_ an integer - 7. there's then stuff after the integer.

and what's "|| n_ptr <=0" supposed to do?
 
Y

Yogi_Bear_79

But there _is_ an integer - 7. there's then stuff after the integer.

It needs to fail if there are any characters other than integers or white
space

and what's "|| n_ptr <=0" supposed to do?

Not needed, removed, same results
 
B

Barry Schwarz

I have the following code:

sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;

The third argument needs an & but I assume that is a typo.

If you want to know whether the data at line is a "well formed"
integer, use strtol which will tell you where it stopped extracting
data. Then examine that char for white space or '\0'.
It only partially works. If the user types a character other than 0-9 to
start the string it fails. However as long as the first character is an
integer it will allow letters in the following places

for Example:

H78 (fails as expected)
78 (works as expected)
7H8 ( doesn't fail as desired)


Remove del for email
 
R

Robert Gamble

Yogi_Bear_79 said:
It needs to fail if there are any characters other than integers or white
space



Not needed, removed, same results

sscanf isn't the best tool for the job for a couple of reasons, not the
least of which is that trying to convert a number of too large a
magnitude results in undefined behavior.

The following program will read a line at a time from stdin and attempt
to convert it to an integer using the strtol function. If the line
contains any invalid characters, an error message is displayed and the
offending character is pointed out. If a range error occurs (the
number was too large/small to store in a long int), an appropriate
message is diaplayed. This version uses 'long int' and accepts
negative numbers, remove the minus sign from the string of allowed
characters if you just want to accept positive numbers. The code is a
little verbose since it implements error checking.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "-0123456789 \t\n")) == strlen(buf)) {
errno = 0;
l = strtol(buf, NULL, 10);
if (errno == ERANGE) {
printf("number out of range!\n");
} else {
printf("number read was %ld\n", l);
}
} else {
while(s--)
putchar(' ');
putchar('^');
printf(" invalid character\n");
}
}
return 0;
}

The strspn function returns the number of characters in the input
string that are in the "allow" string, this must be the same as the
length of the string itself if all the characters are valid, otherwise
we print an error message displaying the location of the offending
character and continue.

The strtol function will skip leading whitespace and attempt to convert
the numeric part of the string to a long int. If an underflow or
overflow occurs then strtol returns LONG_MIN or LONG_MAX respectively
and sets errno to ERANGE. We just check errno and print an error if
appropriate, you could compare l to LONG_MIN or LONG_MAX to determine
the type of range error if desired.

If everything goes well, the converted number is displayed.

A couple of notes:

If the input consists of multiple numbers seperated by whitespace, only
the first one will be converted, you didn't specify what should happen
in this case.

If the input contains only of whitespace or the minus sign the
converted value will be 0.

Entering more than BUF_MAX characters at a time may cause unintended
(but not undefined) behavior.

If an invalid input line contains a tab character before the invalid
character, the error message may not correctly point to the invalid
character.

Addressing these caveats is left as an exercise to the reader.

Robert Gamble
 
P

pemo

Barry said:
The third argument needs an & but I assume that is a typo.

<snip>

From its name, I would guess that n_ptr is set to point to an int and is
defined as

int * n_ptr;
 
Y

Yogi_Bear_79

Barry Schwarz said:
The third argument needs an & but I assume that is a typo.

If you want to know whether the data at line is a "well formed"
integer, use strtol which will tell you where it stopped extracting
data. Then examine that char for white space or '\0'.

I think I understand now. The code as written is reading the line up until
it encounters a non-integer. So 7 & 7H8 both return the same results. And
from yor sufggestion above it will stop extracting at the H which I can use
strtol to examine, and if it stops on anything other than white spaces I
have my error?
 
B

Barry Schwarz

<snip>

From its name, I would guess that n_ptr is set to point to an int and is
defined as

int * n_ptr;

How often do you compare a pointer to less than or equal to zero? If
he had coded *n_ptr <= 0 I would agree with you. Let's agree the code
as written is confusing.


Remove del for email
 
P

Peter Shaggy Haywood

Groovy hepcat Yogi_Bear_79 was jivin' on Thu, 4 May 2006 20:23:56
-0400 in comp.lang.c.
How to sscanf return integer only's a cool scene! Dig it!
I have the following code:

sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;

Presumably n_ptr is a pointer. If not, then not only is it
misleadingly named, but it is also causing undefined behaviour since
sscanf() wants a pointer there. And if n_ptr is a pointer, then the
comparison n_ptr <= 0 makes little or no sense. Perhaps you meant
*n_ptr <= 0?
Also, this line reads in a number (assuming all is correct) and
checks for an error, but takes no action if an error has been
determined to have occurred. Rather pointless, isn't it?
It only partially works. If the user types a character other than 0-9 to
start the string it fails. However as long as the first character is an
integer it will allow letters in the following places

for Example:

H78 (fails as expected)
78 (works as expected)
7H8 ( doesn't fail as desired)

If you must use sscanf() for this, try it this way:

char dummy;
....
if(sscanf(line, "%d%c", n_ptr, dummy) != 1)
{
/* Non-numerical input detected. Handle this error somehow. */
}

--

Dig the even newer still, yet more improved, sig!

http://alphalink.com.au/~phaywood/
"Ain't I'm a dog?" - Ronny Self, Ain't I'm a Dog, written by G. Sherry & W. Walker.
I know it's not "technically correct" English; but since when was rock & roll "technically correct"?
 
J

Joe Smith

"Robert Gamble" <[email protected]
It needs to fail if there are any characters other than integers or white
space



Not needed, removed, same results

sscanf isn't the best tool for the job for a couple of reasons, not the
least of which is that trying to convert a number of too large a
magnitude results in undefined behavior.

The following program will read a line at a time from stdin and attempt
to convert it to an integer using the strtol function. If the line
contains any invalid characters, an error message is displayed and the
offending character is pointed out. If a range error occurs (the
number was too large/small to store in a long int), an appropriate
message is diaplayed. This version uses 'long int' and accepts
negative numbers, remove the minus sign from the string of allowed
characters if you just want to accept positive numbers. The code is a
little verbose since it implements error checking.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "-0123456789 \t\n")) == strlen(buf)) {
errno = 0;
l = strtol(buf, NULL, 10);
if (errno == ERANGE) {
printf("number out of range!\n");
} else {
printf("number read was %ld\n", l);
}
} else {
while(s--)
putchar(' ');
putchar('^');
printf(" invalid character\n");
}
}
return 0;
}

The strspn function returns the number of characters in the input
string that are in the "allow" string, this must be the same as the
length of the string itself if all the characters are valid, otherwise
we print an error message displaying the location of the offending
character and continue.

The strtol function will skip leading whitespace and attempt to convert
the numeric part of the string to a long int. If an underflow or
overflow occurs then strtol returns LONG_MIN or LONG_MAX respectively
and sets errno to ERANGE. We just check errno and print an error if
appropriate, you could compare l to LONG_MIN or LONG_MAX to determine
the type of range error if desired.

If everything goes well, the converted number is displayed.

A couple of notes:

If the input consists of multiple numbers seperated by whitespace, only
the first one will be converted, you didn't specify what should happen
in this case.

If the input contains only of whitespace or the minus sign the
converted value will be 0.

Entering more than BUF_MAX characters at a time may cause unintended
(but not undefined) behavior.

If an invalid input line contains a tab character before the invalid
character, the error message may not correctly point to the invalid
character.

Addressing these caveats is left as an exercise to the reader.

Robert Gamble

I am unable to discern the OP's intent. [snipped from OP:]
sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;
would have wanted to come back with a bunch of concatenated digits. What
if, instead, you wanted a robust means of coming back with an unsigned long?
Elsethread, you hinted that strtol might not be a bad idea.
long strtol(const char *s, char **endp, int base)
The arguments look quite cryptic to me. Joe
 
R

Robert Gamble

Joe said:
"Robert Gamble" <[email protected]
Yogi_Bear_79 wrote:
But there _is_ an integer - 7. there's then stuff after the integer.

It needs to fail if there are any characters other than integers or white
space


and what's "|| n_ptr <=0" supposed to do?

Not needed, removed, same results

sscanf isn't the best tool for the job for a couple of reasons, not the
least of which is that trying to convert a number of too large a
magnitude results in undefined behavior.

The following program will read a line at a time from stdin and attempt
to convert it to an integer using the strtol function. If the line
contains any invalid characters, an error message is displayed and the
offending character is pointed out. If a range error occurs (the
number was too large/small to store in a long int), an appropriate
message is diaplayed. This version uses 'long int' and accepts
negative numbers, remove the minus sign from the string of allowed
characters if you just want to accept positive numbers. The code is a
little verbose since it implements error checking.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "-0123456789 \t\n")) == strlen(buf)) {
errno = 0;
l = strtol(buf, NULL, 10);
if (errno == ERANGE) {
printf("number out of range!\n");
} else {
printf("number read was %ld\n", l);
}
} else {
while(s--)
putchar(' ');
putchar('^');
printf(" invalid character\n");
}
}
return 0;
}

The strspn function returns the number of characters in the input
string that are in the "allow" string, this must be the same as the
length of the string itself if all the characters are valid, otherwise
we print an error message displaying the location of the offending
character and continue.

The strtol function will skip leading whitespace and attempt to convert
the numeric part of the string to a long int. If an underflow or
overflow occurs then strtol returns LONG_MIN or LONG_MAX respectively
and sets errno to ERANGE. We just check errno and print an error if
appropriate, you could compare l to LONG_MIN or LONG_MAX to determine
the type of range error if desired.

If everything goes well, the converted number is displayed.

A couple of notes:

If the input consists of multiple numbers seperated by whitespace, only
the first one will be converted, you didn't specify what should happen
in this case.

If the input contains only of whitespace or the minus sign the
converted value will be 0.

Entering more than BUF_MAX characters at a time may cause unintended
(but not undefined) behavior.

If an invalid input line contains a tab character before the invalid
character, the error message may not correctly point to the invalid
character.

Addressing these caveats is left as an exercise to the reader.

Robert Gamble

I am unable to discern the OP's intent. [snipped from OP:]
sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;
would have wanted to come back with a bunch of concatenated digits.

In a followup the OP indicated that the "|| n_ptr <=0" was extraneous,
I haven't bothered to attempt to discern what its intended purpose
might have been but it doesn't seem to be relevant.
What
if, instead, you wanted a robust means of coming back with an unsigned long?

I am not sure what you mean here, can you clarify?
If you mean modifying my example for unsigned long, just change the
type of l from long to unsigned long, remove the "-" from the list of
allowable characters, and replace the strtol with strtoul.
Elsethread, you hinted that strtol might not be a bad idea.

What do you mean by "elsethread"? I take that to mean elsewhere in
this thread but the only message I have posted in this thread, besides
this one, is the one you are responding to.
long strtol(const char *s, char **endp, int base)
The arguments look quite cryptic to me.

Well, the prototype isn't meant to provide a detailed understanding of
how the function works, especially to someone who has never before
encountered it. The usage of the function is actually very
straightforward, check out your online documentation (man page, etc.),
pick up a good C book, or download n1124 for details.

Robert Gamble
 
J

Joe Smith

Robert Gamble said:
Joe said:
"Robert Gamble" <[email protected]
[Mr. Gamble's source and comments for a slightly different question
snipped]
If everything goes well, the converted number is displayed.
What
if, instead, you wanted a robust means of coming back with an unsigned
long?

[modified source:]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
unsigned long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "0123456789 \t\n")) == strlen(buf)) {
errno = 0;
l = strtoul(buf, NULL, 10);
if (errno == ERANGE) {
printf("number out of range!\n");
} else {
printf("number read was %lu\n", l);
}
} else {
while(s--)
putchar(' ');
putchar('^');
printf(" invalid character\n");
}
}
return 0;
}
/* end source */
Well, the prototype isn't meant to provide a detailed understanding of
how the function works, especially to someone who has never before
encountered it. The usage of the function is actually very
straightforward, check out your online documentation (man page, etc.),
pick up a good C book, or download n1124 for details.


This compiles and seems to behave. The caveats he mentions for when this
situation would fail seem to have as a precondition that the person on the
keyboard had no business touching a computer. The man pages confused me.
Seemed to be all about Linux. I'm always trolling for a good C book. N
1124 sounds like a party. A little too far away for my vacation
preferences. Thanks for the source. Joe
 
R

Robert Gamble

Joe said:
I'm always trolling for a good C book. N
1124 sounds like a party. A little too far away for my vacation
preferences. Thanks for the source. Joe

Check out "C Programming: A Modern Approach" by K. N. King and "C: A
Reference Manual (5th edition)" by Harbison & Steele. These are two of
the best, most comprehensive books out there.

Robert Gamble
 
J

Joe Smith

"Robert Gamble"
Check out "C Programming: A Modern Approach" by K. N. King and "C: A
Reference Manual (5th edition)" by Harbison & Steele. These are two of
the best, most comprehensive books out there.

I just went to Amazon and got Harbison & Steele. I was stunned at how easy
it was, and I didn't have to pick through a bunch of garbage about C flat.
Makes a guy optimistic. Joe
 
D

Dave Thompson

On 4 May 2006 20:34:05 -0700, "Robert Gamble" <[email protected]>
wrote:

sscanf isn't the best tool for the job for a couple of reasons, not the
least of which is that trying to convert a number of too large a
magnitude results in undefined behavior.

The following program will read a line at a time from stdin and attempt
to convert it to an integer using the strtol function. If the line
contains any invalid characters, an error message is displayed and the
offending character is pointed out. If a range error occurs (the
number was too large/small to store in a long int), an appropriate
message is diaplayed. This version uses 'long int' and accepts
negative numbers, remove the minus sign from the string of allowed
characters if you just want to accept positive numbers. The code is a
little verbose since it implements error checking.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "-0123456789 \t\n")) == strlen(buf)) {

Note that this accepts things like "212-535-1000".

A line read by fgets can never include \n, although if this were split
off to a separate function as it probably should be you might want to
retain that for other usages. Or perhaps you want(ed) \r.
errno = 0;
l = strtol(buf, NULL, 10);
if (errno == ERANGE) {
printf("number out of range!\n");
} else {
printf("number read was %ld\n", l);
}
} else {
while(s--)
putchar(' ');
putchar('^');

A cleverer(?) way to do this is: printf ("%*s^", (int)s, "")
or if you like ("%*c", (int)s+1, '^');
or other obvious variations.
printf(" invalid character\n");
}
}
return 0;
}

The strspn function returns the number of characters in the input
string that are in the "allow" string, this must be the same as the
length of the string itself if all the characters are valid, otherwise
we print an error message displaying the location of the offending
character and continue.

The strtol function will skip leading whitespace and attempt to convert
the numeric part of the string to a long int. If an underflow or
overflow occurs then strtol returns LONG_MIN or LONG_MAX respectively
and sets errno to ERANGE. We just check errno and print an error if
appropriate, you could compare l to LONG_MIN or LONG_MAX to determine
the type of range error if desired.
strtol (and friends) will also return a pointer after the part of the
string it successfully converted; that's easier than getting your own
matching/validation logic to match exactly or even just right:
l = strtol (buf, &after, 10);
if( *after != '\0' ) /* wasn't (entirely) whitespace+number */
or
if( strspn (after, white) != strlen (after) )
if( after [strpsn (after, white)] != '\0' )
/* wasn't whitespace+number+whitespace */
etc.

<snip rest>

- David.Thompson1 at worldnet.att.net
 
R

Robert Gamble

Dave said:
On 4 May 2006 20:34:05 -0700, "Robert Gamble" <[email protected]>
wrote:

sscanf isn't the best tool for the job for a couple of reasons, not the
least of which is that trying to convert a number of too large a
magnitude results in undefined behavior.

The following program will read a line at a time from stdin and attempt
to convert it to an integer using the strtol function. If the line
contains any invalid characters, an error message is displayed and the
offending character is pointed out. If a range error occurs (the
number was too large/small to store in a long int), an appropriate
message is diaplayed. This version uses 'long int' and accepts
negative numbers, remove the minus sign from the string of allowed
characters if you just want to accept positive numbers. The code is a
little verbose since it implements error checking.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define BUF_MAX 80

int main (void) {
char buf[BUF_MAX];
long l;
size_t s;
while(fgets(buf, BUF_MAX, stdin) != NULL) {
if ((s = strspn(buf, "-0123456789 \t\n")) == strlen(buf)) {

Note that this accepts things like "212-535-1000".

Good catch, I didn't think of that one but it still meets the OP's
criteria as stated (although I am pretty sure it's not the behavior the
OP would want).
I originally wrote this for unsigned longs and changed it at the last
minute, I don't remember why, and didn't take this into account.
A line read by fgets can never include \n, although if this were split
off to a separate function as it probably should be you might want to
retain that for other usages. Or perhaps you want(ed) \r.

You might want to review your documentation on the fgets function.
A cleverer(?) way to do this is: printf ("%*s^", (int)s, "")
or if you like ("%*c", (int)s+1, '^');
or other obvious variations.

More clever perhaps, but less clear.
printf(" invalid character\n");
}
}
return 0;
}

The strspn function returns the number of characters in the input
string that are in the "allow" string, this must be the same as the
length of the string itself if all the characters are valid, otherwise
we print an error message displaying the location of the offending
character and continue.

The strtol function will skip leading whitespace and attempt to convert
the numeric part of the string to a long int. If an underflow or
overflow occurs then strtol returns LONG_MIN or LONG_MAX respectively
and sets errno to ERANGE. We just check errno and print an error if
appropriate, you could compare l to LONG_MIN or LONG_MAX to determine
the type of range error if desired.
strtol (and friends) will also return a pointer after the part of the
string it successfully converted; that's easier than getting your own
matching/validation logic to match exactly or even just right:
l = strtol (buf, &after, 10);
if( *after != '\0' ) /* wasn't (entirely) whitespace+number */
or
if( strspn (after, white) != strlen (after) )
if( after [strpsn (after, white)] != '\0' )
/* wasn't whitespace+number+whitespace */
etc.

Right. Given the deficiency you mentioned at the beginning of your
post, the above would probably be better solution.

Robert Gamble
 
R

RSoIsCaIrLiIoA

I have the following code:

sscanf(line, "%d", n_ptr) !=1 || n_ptr <=0;

It only partially works. If the user types a character other than 0-9 to
start the string it fails. However as long as the first character is an
integer it will allow letters in the following places

for Example:

H78 (fails as expected)
78 (works as expected)
7H8 ( doesn't fail as desired)

you have to write a sscanf like function
a better sscanf
 
D

Dave Thompson

Dave said:
On 4 May 2006 20:34:05 -0700, "Robert Gamble" <[email protected]>
wrote: [about strspn for number-token including space \t \n]
A line read by fgets can never include \n, although if this were split
off to a separate function as it probably should be you might want to
retain that for other usages. Or perhaps you want(ed) \r.

You might want to review your documentation on the fgets function.
Aargh! Part of my brain was thinking but didn't finish getting to my
fingers: can't have \n _embedded_, only at the end, which I often
handle differently/specially -- probably including this case, although
as already noted I would probably have factored and (hopefully)
reusable "get line to buffer" and "parse number from buffer" instead
of "parse number from line" so avoiding the issue.
More clever perhaps, but less clear.
I for one can think of it either as "print n spaces and marker" where
the loop is (much) more obvious or as "print marker at column n" (like
Fortran or BASIC's 'tab' format) where the %* makes sense to me.

But objectively it is certainly rarer and less well known. Which may
well be sufficient reason to avoid it.

<snip other points>

- David.Thompson1 at worldnet.att.net
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top