I want to parse string to integer.
The string can contain any data, including invalid or float integers. This is my code which shows how I'm using sscanf():
errno = 0;
uint32_t temp;
int res = sscanf(string, "%"SCNu32, &temp);
if (0 != errno || 1 != res)
{
return HEX_ECONVERSION;
}
where string passed as argument.
I've expected that this code would fail on "3.5" data. But unfortunately, sscanf() truncate "3.5" to 3 and write it to temp integer.
What's wrong?
Am I improperly using sscanf()? And how to achieve this task by using sscanf, without writing hand-written parser (by directly calling strtoul() or similar).
3 is a valid integer. Unfortunately sscanf is not complicated enough to look ahead and detect that 3.5 is a float and not give you any result.
Try using strtol and testing the return pointer to see if it parsed the entire string.
Using the "%n" records where the scan is in the buffer.
We can use it to determine what stopped the scan.
int n;
int res = sscanf(string, "%"SCNu32 " %n", &temp, &n);
if (0 != errno || 1 != res || string[n] != '\0')
return HEX_ECONVERSION;
Appending " %n" says to ignore following white-space, then note the buffer position. If there is not additional junk like ".5", the string[n] will point to the null terminator.
Be sure to test n after insuring temp was set. This was done above with 1 != res.
"%n" does not affect the value returned by sscanf().
sscanf only parses the part of the string that it can. You can add another format to it such as "%d%s". If the %s specifier captures a non-empty string (such as .5), throw an error.
Related
I'm trying to get an integer number from command line without scanf() but using justfgets(), how can I filter the fgets() contentsreporting an error if I insert a character or a string? The problem is that when I insert something different like a character or a string the atoi()function (essential to do some operations in my algorithm) converts me that string to 0, whilst I'd prefer to exit if the value inserted is different from an integer.
Here's a code part:
.....
char pos[30];
printf("\n Insert a number: ");
fgets (pos, sizeof(pos), stdin);
if (atoi(pos) < 0) //missing check for string character
exit(1);
else{
printf ("%d\n", atoi(pos)); //a string or character converted through atoi() gives 0
}
int number = atoi(pos);
......
As commenters have said, use strtol() not atoi().
The problem with strtol() is that it will only give an ERANGE error (as per the specification) when the converted number will not fit in a long-type. So if you ask it to convert " 1" it gives 1. If you ask it to convert "apple", it returns 0 and sets endptr to indicate an error.
Obviously you need to decide if " 12" is going to be acceptable input or not — strtol() will happily skip the leading white space.
EDIT: Function updated to better handle errors via the endptr.
// Convert the given <text> string to a decimal long, in <value>
// Allow a string of digits, or white space then digits
// returns 1 for OK, or 0 otherwise
int parseLong( const char *text, long *value )
{
int rc = 0; // fail
char *endptr; // used to determine failure
if ( text && value )
{
errno = 0; // Clear any errors
*value = strtol( text, &endptr, 10 ); // Do the conversion
// Check that conversion was performed, and
// that the value fits in a long
if ( endptr != text && errno != ERANGE )
{
rc = 1; // success
}
}
return rc;
}
First, you have to keep in mind that characters are not essentially alpha characters; be precise.
I think what you're looking for is an "is integer" function.
In the standard C library ctype.h there are functions called isalpha and isdigit.
https://www.programiz.com/c-programming/library-function/ctype.h/isalpha
So you could make a function that verifies if a char * contains only numeric characters.
int str_is_only_numeric(const char *str) {
int i = 0;
while (str[i] != '\0') {
if (isdigit(str[i++]) == 0) {
return -1;
}
}
return 0;
}
Here's a working example of the function: https://onlinegdb.com/SJBdLdy78
I solved on my own using strcspn()before checking through isdigit()the integer type, without strcspn() it'd have returned always -1
I'm using atoi to convert a string integer value into integer.
But first I wanted to test different cases of the function so I have used the following code
#include <stdio.h>
int main(void)
{
char *a ="01e";
char *b = "0e1";
char *c= "e01";
int e=0,f=0,g=0;
e=atoi(a);
f=atoi(b);
g=atoi(c);
printf("e= %d f= %d g=%d ",e,f,g);
return 0;
}
this code returns e= 1 f= 0 g=0
I don't get why it returns 1 for "01e"
that's because atoi is an unsafe and obsolete function to parse integers.
It parses & stops when a non-digit is encountered, even if the text is globally not a number.
If the first encountered char is not a space or a digit (or a plus/minus sign), it just returns 0
Good luck figuring out if user input is valid with those (at least scanf-type functions are able to return 0 or 1 whether the string cannot be parsed at all as an integer, even if they have the same behaviour with strings starting with integers) ...
It's safer to use functions such as strtol which checks that the whole string is a number, and are even able to tell you from which character it is invalid when parsing with the proper options set.
Example of usage:
const char *string_as_number = "01e";
char *temp;
long value = strtol(string_as_number,&temp,10); // using base 10
if (temp != string_as_number && *temp == '\0')
{
// okay, string is not empty (or not only spaces) & properly parsed till the end as an integer number: we can trust "value"
}
else
{
printf("Cannot parse string: junk chars found at %s\n",temp);
}
You are missing an opportunity: Write your own atoi. Call it Input2Integer or something other than atoi.
int Input2Integer( Str )
Note, you have a pointer to a string and you will need to establish when to start, how to calculate the result and when to end.
First: Set return value to zero.
Second: Loop over string while it is not null '\0'.
Third: return when the input character is not a valid digit.
Fourth: modify the return value based on the valid input character.
Then come back and explain why atoi works the way it does. You will learn. We will smile.
I'm trying to read multiple lines of vertices with varying length using fgets and sscanf.
(1,6),(2,6),(2,9),(1,9)
(1,5)
My program goes into an infinite loop stuck within the first vertex.
char temp3[255];
while(fgets(temp3, 255, fp)!= NULL){
printf("Polygon %d: ", polycount);
while(sscanf(temp3, "(%d,%d)", &polygonx[polycount][vertcount], &polygony[polycount][vertcount]) != EOF){
sscanf(temp3, ",");
printf("(%d,%d),",polygonx[polycount][vertcount], polygony[polycount][vertcount]);
vertcount++;
}
vertcounts[polycount] = vertcount;
vertcount = 0;
polycount++;
}
I must be able to feed the x and y values of the vertices into the polygon arrays, so i'm stuck with using sscanf. I'm also having a problem since I cant find anything on the internet that scans varying numbers of elements per line.
It's because this
while(sscanf(temp3, "(%d,%d)",
&polygonx[polycount][vertcount], &polygony[polycount][vertcount]) != EOF)
{
}
is never going to be true I think, because scanf() returns the number of parameters succesfuly scanned, I would do this instead
while(sscanf(temp3, "(%d,%d)",
&polygonx[polycount][vertcount], &polygony[polycount][vertcount]) == 2)
{
}
Your code doesn't work because it does not satisfy the condition for sscanf() to return EOF, the following is from the manual page referenced at the end
The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs. EOF is also returned if a read error occurs, in which case the error indicator for the stream (see ferror(3)) is set, and errno is set to indicate the error.
So it appears that you are not reaching the end if input before the first successful conversion or a matching failure occurs, which makes sense according to the contents of the file. And the second part applies only to file streams of course.
And instead of the sscanf(temp3, ",") which doesn't do what you think, you could do it like this
next = strchr(temp3, ',');
if (next != NULL)
temp3 = next + 1;
else
/* you've reached the end here */
This is a suggestion on how to parse this file
#include <stdio.h>
#include <string.h>
int
main(void)
{
const char temp3[] = "(1,6),(2,6),(2,9),(1,9)\n(1,5)";
char *source;
int x, y;
int count;
source = temp3;
while (sscanf(source, "(%d,%d)%*[^(]%n", &x, &y, &count) == 2)
{
/* this is just for code clarity */
polygonx[polycount][vertcount] = x;
polygony[polycount][vertcount] = y;
/* Process here if needed, and then advance the pointer */
source += count;
}
return 0;
}
The "%n" specifier captures the number of characters scanned so far, so you can use it to advance the pointer to the las position scanned in the source string.
And the "%*[^(" will skip all characters until the next '(' is found.
Please refer to sscanf(3) for more information on the "%n" specifier, and the %[ specifier.
If successfully read sscanf will return 2 in this case . sscanf returns numbers of variables filled.
Check if it returns 2 which will indicate success here .
while(sscanf(temp3,"(%d,%d)",&polygonx[polycount][vertcount],&polygony[polycount]][vertcount]) != EOF)
Instead of this , check like this -
while(sscanf(temp3,"(%d,%d)%*c",&polygonx[polycount][vertcount],&polygony[polycount]][vertcount])== 2)
^ to exclude the comma after it
also to ignore ',' after the coordinates , you use -
sscanf(temp3, ",");
is not correct . In the above sscanf you can read it and discard it as well by using %*c specifier.
The man page states that the signature of sscanf is
sscanf(const char *restrict s, const char *restrict format, ...);
I have seen an answer on SO where a function in which sscanf is used like this to check if an input was an integer.
bool is_int(char const* s) {
int n;
int i;
return sscanf(s, "%d %n", &i, &n) == 1 && !s[n];
}
Looking at !s[n] it seems to suggest that we check if sscanf scanned the character sequence until the termination character \0. So I assume n stands for the index where sscanf will be in the string s when the function ends.
But what about the variable i? What does it mean?
Edit:
To be more explicit: I see the signature of sscanf wants a pointer of type char * as first parameter. A format specifier as seconf parameter so it knows how to parse the character sequence and as much variables as conversion specifiers as next parameters. I understand now that i is for holding the parsed integer.
Since there is only one format specifier, I tried to deduce the function of n.
Is my assumption above for n correct?
Looks like the op has his answer already, but since I bothered to look this up for myself and run the code...
From "C The Pocket Reference" (2nd Ed by Herbert Shildt) scanf() section:
%n Receives an integer of value equal to the number of characters read so far
and for the return value:
The scanf() function returns a number equal to the number of the number of fields
that were successfully assigned values
The sscanf() function works the same, it just takes it's input from the supplied buffer argument ( s in this case ). The "== 1" test makes sure that only one integer was parsed and the !s[n] makes sure the input buffer is well terminated after the parsed integer and/or that there's really only one integer in the string.
Running this code, an s value like "32" gives a "true" value ( we don't have bool defined as a type on our system ) but s as "3 2" gives a "false" value because s[n] in that case is "2" and n has the value 2 ( "3 " is parsed to create the int in that case ). If s is " 3 " this function will still return true as all that white space is ingored and n has the value of 3.
Another example input, "3m", gives a "false" value as you'd expect.
Verbatim from sscanf()'s man page:
Conversions
[...]
n
Nothing is expected; instead, the number of characters
consumed thus far from the input is stored through the next pointer,
which must be a pointer to int. This is not a
conversion, although it can be suppressed with the * assignment-suppression character. The C
standard says: "Execution of
a %n directive does not increment the assignment count returned at the completion of
execution" but the Corrigendum seems to contradict this. Probably it is wise not
to make any assumptions on the effect of %n conversions on the return value.
I would like to point out that the original code is buggy:
bool is_int(char const* s) {
int n;
int i;
return sscanf(s, "%d %n", &i, &n) == 1 && !s[n];
}
I will explain why. And I will interpret the sscanf format string.
First, buggy:
Given input "1", which is the integer one, sscanf will store 1 into i. Then, since there is no white space after, sscanf will not touch n. And n is uninitialized. Because sscanf set i to 1, the value returned by sscanf will be 1, meaning 1 field scanned. Since sscanf returns 1, the part of the expression
sscanf(s, "%d %n", &i, &n) == 1
will be true. Therefore the other part of the && expression will execute. And s[n] will access some random place in memory because n is uninitialized.
Interpreting the format:
"%d %n"
Attempts to scan a number which may be a decimal number or an integer or a scientific notation number. The number is an integer, it must be followed by at least one white space. White space would be a space, \n, \t, and certain other non-printable characters. Only if it is followed by white space will it set n to the number of characters scanned to that point, including the white space.
This code might be what is intended:
static bool is_int(char const* s)
{
int i;
int fld;
return (fld = sscanf(s, "%i", &i)) == 1;
}
int main(int argc, char * argv[])
{
bool ans = false;
ans = is_int("1");
ans = is_int("m");
return 0;
}
This code is based on, if s is an integer, then sscanf will scan it and fld will be exactly one. If s is not an integer, then fld will be zero or -1. Zero if something else is there, like a word; and -1 if nothing is there but an empty string.
variable i there means until it has read an integer vaalue.
what are you trying to ask though? Its not too clear! the code will (try to ) read an integer from the string into 'i'
I use strtok to get the tokens in a line from stdin.
fgets(line,MAXCOLS,stdin);
printf("line:%s\n",line);
ch = strtok(line," ");
while(ch != NULL)
{
printf("%s\n",ch);
ch = strtok(NULL," ");
}
But how do I find out if my ch is a float, a alphanumeric value or a special character?
Your ch is never any of those things. It is always a pointer to an array of characters.
You might want to know whether those characters are a textual representation of a numeric value. To that end, you can run strtol() or strtod() on the tokens and see if you succeed. Note that something like 12.34 will be read successfully both as an integer and as a floating point number, so you should supply the second argument to those functions and check that you really reach the end of the token (as opposed to only converting an initial part of the token):
char * e;
long int n = strtol(ch, &e, 0);
if (*e != 0) { /* error? */ }
You can use the is* family of functions, e.g. isalpha().
But note, this only tells you about the value of one character. A character is just a character, it's not, for instance, a float.