Reputation: 560
Checking if it can be an int is easy enough -- just check that every digit is between '0'
and '9'
. But a float is harder. I found this, but none of the answers really work. Consider this code snippet, based on the top (accepted) answer:
float f;
int ret = sscanf("5.23.fkdj", "%f", &f);
printf("%d", ret);
1
will be printed.
Another answer suggested using strpbrk
, to check if certain illegal characters are present, but that wouldn't work either because 5fin7
wouldn't be legal, but inf
would.
Yet another answer suggested checking the output of strtod. But consider this:
char *number = "5.53 garbanzo beans"
char *foo;
strtod(number, &foo);
printf("%d", isspace(*foo) || *foo == '\0'));
It'll print 1
. But I don't want to remove the isspace
call entirely, because " 5.53 "
should be a valid number.
Is there a good, elegant, idiomatic way to do what I'm trying to do?
Upvotes: 20
Views: 12385
Reputation: 726709
The first answer should work if you combine it with %n
, which is the number of characters read:
int len;
float ignore;
char *str = "5.23.fkdj";
int ret = sscanf(str, "%f %n", &ignore, &len);
printf("%d", ret==1 && !str[len]);
!str[len]
expression will be false if the string contains characters not included in the float
. Also note space after %f
to address trailing spaces.
Upvotes: 16
Reputation: 754190
This code is closely based on the answer by dasblinkenlight. I proffer it as food for thought. Some of the answers it gives may not be what you wanted.
#include <stdio.h>
#include <string.h>
static void test_float(const char *str)
{
int len;
float dummy = 0.0;
if (sscanf(str, "%f %n", &dummy, &len) == 1 && len == (int)strlen(str))
printf("[%s] is valid (%.7g)\n", str, dummy);
else
printf("[%s] is not valid (%.7g)\n", str, dummy);
}
int main(void)
{
test_float("5.23.fkdj"); // Invalid
test_float(" 255. "); // Valid
test_float("255.123456"); // Valid
test_float("255.12E456"); // Valid
test_float(" .255 "); // Valid
test_float(" Inf "); // Valid
test_float(" Infinity "); // Valid
test_float(" Nan "); // Valid
test_float(" 255 "); // Valid
test_float(" 0x1.23P-24 "); // Valid
test_float(" 0x1.23 "); // Valid
test_float(" 0x123 "); // Valid
test_float("abc"); // Invalid
test_float(""); // Invalid
test_float(" "); // Invalid
return 0;
}
Testing on a Mac running macOS Sierra 10.12.6 using GCC 7.1.0 as the compiler, I get the output:
[5.23.fkdj] is not valid (5.23)
[ 255. ] is valid (255)
[255.123456] is valid (255.1235)
[255.12E456] is valid (inf)
[ .255 ] is valid (0.255)
[ Inf ] is valid (inf)
[ Infinity ] is valid (inf)
[ Nan ] is valid (nan)
[ 255 ] is valid (255)
[ 0x1.23P-24 ] is valid (6.775372e-08)
[ 0x1.23 ] is valid (1.136719)
[ 0x123 ] is valid (291)
[abc] is not valid (0)
[] is not valid (0)
[ ] is not valid (0)
The hexadecimal numbers are likely to be particularly problematic. The various forms of infinity and not-a-number could be troublesome too. And the one example with an exponent (255.12E456
) overflows float
and generates an infinity — is that really OK?
Most of the problems raised here are definitional — that is, how do you define what you want to be acceptable. But note that strtod()
would accept all the valid strings (and a few of the invalid ones, but other testing would reveal those problems).
Clearly, the test code could be revised to use an array of a structure containing a string and the desired result, and this could be used to iterate through the test cases shown and any extras that you add.
The cast on the result of strlen()
avoids a compilation warning (error because I compile with -Werror
) — comparison between signed and unsigned integer expressions [-Werror=sign-compare]
. If your strings are long enough that the result from strlen()
overflows a signed int
, you've got other problems pretending they're valid values. OTOH, you might want to experiment with 500 digits after a decimal point — that's valid.
This code notes the comments made to dasblinkenlight's answer:
Upvotes: 6
Reputation: 153660
Is there a way to check if a string can be a float?
A problem with the sscanf(...,"%f")
approach is on overflow, which is UB. Yet it is commonly handled nicely.
Instead use float strtof(const char * restrict nptr, char ** restrict endptr);
int float_test(const char *s) {
char *ednptr;
errno = 0;
float f = strtof(s, &endptr);
if (s == endptr) {
return No_Conversion;
}
while (isspace((unsigned char) *endptr)) { // look past the number for junk
endptr++;
}
if (*endptr) {
return Extra_Junk_At_End;
}
// If desired
// Special cases with with underflow not considered here.
if (errno) {
return errno; // likely under/overflow
}
return Success;
}
Upvotes: 6
Reputation: 144820
This is a variation on the code fragment posted by dasblinkenlight that is slightly simpler and more efficient as strlen(str)
could be costly:
const char *str = "5.23.fkdj";
float ignore;
char c;
int ret = sscanf(str, "%f %c", &ignore, &c);
printf("%d", ret == 1);
Explanation: sscanf()
returns 1
if and only if a float was converted, followed by optional white space and no other character.
Upvotes: 4
Reputation: 67602
Maybe this? Not very good but may do the job. Returns -1 on error 0 on no conversions done and > 0 with converted numbers flags set.
#define INT_CONVERTED (1 << 0)
#define FLOAT_CONVERTED (1 << 1)
int ReadNumber(const char *str, double *db, int *in)
{
int result = (str == NULL || db == NULL || in == NULL) * -1;
int len = 0;
char *tmp;
if (result != -1)
{
tmp = (char *)malloc(strlen(str) + 1);
strcpy(tmp, str);
for (int i = strlen(str) - 1; i >= 0; i--)
{
if (isspace(tmp[i]))
{
tmp[i] = 0;
continue;
}
break;
}
if (strlen(tmp))
{
if (sscanf(tmp, "%lf%n", db, &len) == 1 && strlen(tmp) == len)
{
result |= FLOAT_CONVERTED;
}
if (sscanf(tmp, "%d%n", in, &len) == 1 && strlen(tmp) == len)
{
result |= INT_CONVERTED;
}
}
free(tmp);
}
return result;
}
Upvotes: 0
Reputation: 35154
You could check if - after having read a value using strtod
- the remainder consists solely of white spaces. Function strspn
can help here, and you can even define "your personal set of white spaces" to consider:
int main() {
char *number = "5.53 garbanzo beans";
char *foo;
double d = strtod(number, &foo);
if (foo == number) {
printf("invalid number.");
}
else if (foo[strspn(foo, " \t\r\n")] != '\0') {
printf("invalid (non-white-space) trailing characters.");
}
else {
printf("valid number: %lf", d);
}
}
Upvotes: 10