在C语言中,有没有一种方法可以检查一个字符串是否可以转换为浮点数?

19

检查是否可以转换成整数很容易 - 只需检查每个数字是否在'0''9'之间即可。但是转换成浮点数更加困难。我找到了这个,但是其中的回答都不太好用。考虑下面这段基于顶部(被接受的)答案的代码片段:

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

1将被打印出来。


另一个答案建议使用strpbrk检查是否存在某些非法字符,但这也不起作用,因为5fin7不合法,但inf是合法的。


另一个答案建议检查strtod的输出。但请考虑以下情况:

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

strtod(number, &foo);

printf("%d", isspace(*foo) || *foo == '\0'));

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


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


2
正则表达式怎么样? - SBS
3
在这种情况下,您可能需要先对序列进行标记化处理,这意味着没有前导或尾随空格,肯定不会出现以数字开头、后面跟空格并具有其他内容的 5.0 米 这样的情况。 - Daniel H
5
您的“简易”整数测试不包括前导“-”符号。 - stark
2
作为一般规则,我发现如果想编写语法,就应该编写语法。每次尝试用一些聪明的 C 代码绕过编写语法的步骤时,我总是可靠地失望。我们发明语法的正式表示和解析它们的软件(LEX/YACC、BISON、ANTLR、Boost.Spirit 等)是有原因的。 - Cort Ammon
1
我不确定你是在寻找“一般的浮点数”还是“在C语言中被视为浮点常量”。后者可能会包括十六进制浮点数等,但在前一种情况下可能是不需要的。 - pipe
显示剩余15条评论
6个回答

15

如果你将它与%n结合使用,第一个答案应该能够起作用,%n是读取的字符数:

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]表达式将会在字符串中包含不属于float的字符时为false,注意在%f后添加空格以解决末尾空格问题。

演示


1
@Elronnd 我在 %f 前后添加了空格以解决这个问题(演示)。 - Sergey Kalinichenko
太棒了。我接受了你的答案,因为我认为它更加优雅。 - Moonchild
2
前导空格是无害和对称的,但是不必要的;%f(像所有数值转换规范一样)无论你是否想要它这样做,都会跳过前导空格。 - Jonathan Leffler
2
@JonathanLeffler 哇,这真是个好发现!非常感谢!我猜我可以通过将 len 设置为 strlen(str)+1 来省略星号,但那样会非常难以阅读和理解,并且它还会强制我特殊处理空字符串,所以我会采纳您的建议并使用一个被忽略的变量。谢谢! - Sergey Kalinichenko
2
实际上,如果 str 为空,则 ret 可以是 EOF。这里有一个修复方法:printf("%d", ret == 1 && str[len] == '\0"); - chqrlie
显示剩余11条评论

10

使用strtod读取一个值后,您可以检查余数是否仅由空格组成。函数strspn可以帮助您,您甚至可以定义“您的个人空格集”来考虑:

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

这个程序对我所有的测试用例都有效,除了一个。它认为 235. 是一个数字,但实际上不是。 - Moonchild
我刚刚被告知,尾随的 . 是可以接受的。 - Moonchild
为什么不把“255.”视为浮点数。strtod()函数会接受它;它并不强制要求小数点后必须有一位数字。如果小数点存在,可以规定小数点前后都必须有数字,但这需要额外的测试,因为C中,"255."和".255"都是合法的浮点数,而strtod()函数会接受它们。但是,如果您要强制执行(数字之前和之后的规则),则必须声明它。 - Jonathan Leffler
根据C编译器,@Elronnd 235是一个有效的浮点数。 - Sergey Kalinichenko
2
嗯 - 这取决于:在C语言中,double valid = 553.;是有效的。 - Stephan Lechner
显示剩余2条评论

6

有没有办法检查一个字符串是否可以作为浮点数?

sscanf(...,"%f")的问题在于如果溢出会有未定义行为。但是通常情况下会处理得很好。

相反,可以使用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;
}

5

这段代码是基于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;
}

在运行macOS Sierra 10.12.6的Mac上,使用GCC 7.1.0作为编译器进行测试,我得到了以下输出:
[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溢出,则假装它们是有效值时还有其他问题。另一方面,您可能想尝试小数点后500位——那是有效的。
此代码记录了对dasblinkenlight答案的评论:

4

这是对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() 仅当转换为浮点数后,其后跟可选空格且没有其他字符时,才返回1

这里是最佳的sscanf()答案。 - chux - Reinstate Monica

0
也许这个?虽然不是很好但可能能完成任务。在错误时返回-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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接