有没有办法检查一个字符串是否可以是 C 中的浮点数?

Is there a way to check if a string can be a float in C?

检查它是否可以是 int 很容易——只需检查每个数字都在 '0''9' 之间。但是漂浮物更难。我找到 this,但 none 的答案确实有效。考虑这个基于顶部(已接受)答案的代码片段:

float f;
int ret = sscanf("5.23.fkdj", "%f", &f);
printf("%d", ret);

1 将被打印出来。


Another 建议使用 strpbrk 的答案来检查是否存在某些非法字符,但这也行不通,因为 5fin7 不合法,但是 inf 会。


Yet another 回答建议检查 strtod 的输出。但考虑一下:

char *number = "5.53 garbanzo beans"
char *foo;

strtod(number, &foo);

printf("%d", isspace(*foo) || *foo == '[=11=]'));

它将打印 1。但我不想完全删除 isspace 调用,因为 " 5.53 " 应该是一个有效数字。


有没有一种好的、优雅的、惯用的方法来做我想做的事情?

如果将第一个答案与 %n 结合使用,第一个答案应该有效,这是读取的字符数:

int len;
float ignore;
char *str = "5.23.fkdj";
int ret = sscanf(str, "%f %n", &ignore, &len);
printf("%d", ret==1 && !str[len]);
如果字符串包含 float 中未包含的字符,

!str[len] 表达式将为假。另请注意 %f 之后的 space 以解决尾随 spaces.

Demo

您可以检查 - 在使用 strtod 读取一个值后 - 其余部分是否仅由空格组成。函数 strspn 可以在这里提供帮助,您甚至可以定义 "your personal set of white spaces" 来考虑:

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")] != '[=10=]') {
        printf("invalid (non-white-space) trailing characters.");
    }
    else {
        printf("valid number: %lf", d);
    }
}

也许是这个?不是很好,但可以胜任。 Returns -1 错误 0 未完成转换且 > 0 已设置转换数字标志。

#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;
}

此代码与 by dasblinkenlight 密切相关。我把它作为思考的食物。它给出的一些答案可能不是你想要的。

#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;
}

使用 GCC 7.1.0 作为编译器在 Mac 运行 macOS Sierra 10.12.6 上进行测试,我得到了输出:

[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)

十六进制数可能特别有问题。各种形式的无穷大和非数字也可能很麻烦。一个带有指数 (255.12E456) 的示例溢出 float 并生成无穷大 — 真的可以吗?

这里提出的大部分问题都是定义性的——也就是说,你如何定义你想要接受的东西。但请注意 strtod() 会接受所有有效字符串(以及一些无效字符串,但其他测试会揭示这些问题)。

显然,可以修改测试代码以使用包含字符串和所需结果的结构数组,这可以用于遍历显示的测试用例和您添加的任何额外内容。

strlen() 结果的转换避免了编译警告(错误是因为我使用 -Werror 编译)— comparison between signed and unsigned integer expressions [-Werror=sign-compare]。如果您的字符串足够长,以至于 strlen() 的结果溢出了带符号的 int,您就会遇到其他问题,假装它们是有效值。 OTOH,您可能想尝试使用小数点后 500 位数字 - 这是有效的。

这段代码记录了对 dasblinkenlight 的回答的评论:

  • — 现在在答案中采用。

这是 dasblinkenlight 发布的代码片段的一个变体,它更简单、更高效,因为 strlen(str) 可能代价高昂:

const char *str = "5.23.fkdj";
float ignore;
char c;
int ret = sscanf(str, "%f %c", &ignore, &c);
printf("%d", ret == 1);

解释:sscanf() returns 1 当且仅当转换了一个浮点数,后跟可选的白色 space 并且没有其他字符。

Is there a way to check if a string can be a float?

sscanf(...,"%f") 方法的一个问题是溢出,这是 UB。但它通常处理得很好。

改为使用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;
}