【问题标题】:Is this a bad way to check if a string represents a floating point number?这是检查字符串是否表示浮点数的不好方法吗?
【发布时间】:2017-12-21 01:42:25
【问题描述】:

在网上找了一圈,没有找到让我满意的解决方案,所以我自己尝试了一下。但是现在在一个讲座中也有人说调用函数和等待返回可能会导致堆栈溢出,那么这是一个坏主意吗?我使用此函数来检查 argv[1] 是否为浮点数。循环会更好吗?还是有更直观的方法?必须正好有一个点,并且后面必须至少有一个数字,对吗?

#include <stdbool.h>
#include <ctype.h>

 /**
 * checks if string is floating point number
 * please call function with pointCounter=0 and digitAfterPoint=false
 */
bool isFloatString(char *s, int pointCounter, bool digitAfterPoint) 
{                                                                   

    if (isdigit(*s))
    {
        if(pointCounter==1)
        {
            digitAfterPoint=true;
        }
        return  isFloatString(s+1, pointCounter, digitAfterPoint);
    }

    else if (*s == '.' && pointCounter==0)
    {
        return isFloatString(s+1, pointCounter+1,digitAfterPoint);
    }
    else if (*s == '\0' && digitAfterPoint)
    {
        return true;
    }
    else
    {
        return false;
    }

}

【问题讨论】:

  • 除了不同的环境可能对什么构成浮点数有不同的想法。例如,c++ 编译器对以下内容非常满意:const float myVal = 3.f; 使用 sscanf 有什么问题吗?
  • recursive function 调用更热衷于堆栈溢出。
  • 0x2a.bcp4, -2.21l, 3.0e4L, 3e-4 也是 C 中有效浮点文字的示例
  • @enhzflep:sscanf(实际上,任何解析数字的 C 库函数)是区域设置感知的,并且区域设置是进程范围的而不是线程安全的(所以你不能只切换到 C时刻然后将其切换回来),因此以独立于语言环境的方式解析数据(例如解析 JSON)的唯一方法是自己完成这项工作。这是 IMO C 标准库中最可悲的设计错误之一,因为与许多其他人不同,没有简单的解决方法。
  • 您可以使用strtod() 进行转换。它会告诉您停止转换的位置,因此您可以决定数字后的尾随碎片是否可以接受。 OTOH,它会跳过前导空白;如果您不想接受,则必须在调用strtod() 之前检测到它——这并不难,但如果它是一个问题,只需将其视为一个问题。

标签: c


【解决方案1】:

对于 999 位和一个点,每个返回地址有 1000 次递归调用,堆栈上有 3 个参数。我会觉得没问题。然而,非递归迭代解决方案消除了状态参数,并且更易于阅读(仅在这种情况下)。

bool isFloatString(char *s)
{
    int pointCounter = 0;
    bool digitAfterPoint = false;
    while (*s != '\0')
    {
        if (isdigit(*s))
            digitAfterPoint = pointCounter == 1;
        }
        else if (*s == '.' && pointCounter == 0)
        {
            ++pointCounter;
        }
        else
        {
            return false;
        }
        ++s;
    }
    return digitAfterPoint;
}

注意:递归解决方案会受到恶意堆栈溢出的影响。


@MatteoItalia 正确地指出只有尾递归(对结果不做任何事情),因此任何成熟的 C/C++ 编译器都会将递归转换为跳转(迭代)。这是他的反汇编(也参见评论中的链接)。

isFloatString(char*, int, bool):
  movsx ecx, BYTE PTR [rdi]
  mov r9d, edx
  mov r8d, ecx
  sub ecx, 48
  cmp ecx, 9
  jbe .L23
  cmp r8b, 46
  je .L24
  test r8b, r8b
  sete al
  and eax, edx
  ret
.L24:
  xor eax, eax
  test esi, esi
  je .L25
.L1:
  rep ret
.L23:
  movsx eax, BYTE PTR [rdi+1]
  mov ecx, eax
  sub eax, 48
  cmp esi, 1
  je .L26
  cmp eax, 9
  movzx edx, dl
  jbe .L10
  cmp cl, 46
  je .L27
.L8:
  test cl, cl
  sete al
  and eax, r9d
  ret
.L26:
  cmp eax, 9
  jbe .L28
  xor eax, eax
  cmp cl, 46
  mov r9d, 1
  jne .L8
  jmp .L1
.L28:
  mov edx, 1
.L10:
  add rdi, 2
  jmp isFloatString(char*, int, bool)
.L25:
  movzx edx, dl
  add rdi, 1
  mov esi, 1
  jmp isFloatString(char*, int, bool)
.L27:
  xor eax, eax
  test esi, esi
  jne .L1
  add rdi, 2
  mov esi, 1
  jmp isFloatString(char*, int, bool)

【讨论】:

  • 值得注意的是,OP 解决方案实际上是尾递归的。虽然确实没有严格保证,但我希望任何值得使用的编译器都会将调用转换为跳转。
  • @MatteoItalia true(不对结果做任何事情),但它看起来几乎不是尾递归的,不确定的编译器行为至少应该为堆栈溢出敲响警钟。但是,我很想看到生成代码的人的回答,也许是 LLVM。 (不逼你——我也没有时间。)
  • 当然,事实上这主要是技术问题;此外,对 OP 解决方案最明显的反驳是您在回答中所说的 - 在这种情况下,迭代解决方案更容易阅读。对于代码,我尝试使用伟大的 gcc.godbolt.org 来提供它,但不幸的是,从移动设备那里粘贴代码被严重破坏了。我会在 15 分钟内添加它,这正是使用真正计算机的时间。
  • ... 这里是 - godbolt.org/g/MHxEDZ 。没有call,只有直接jmps 到函数的开头。
  • @MatteoItalia 非常感谢。
【解决方案2】:

检查一个字符串是否代表一个浮点数?

C 标准库使用strtof()strtod()strtold() 提供了一个简单而可靠的解决方案

// leading whitespace OK, trailing text OK, over/underflow OK
bool isFloatString_Simple(const char *s) {
  char *endptr;
  strtof(s, &endptr);
  return endptr > s;
}

前导空格可以吗?
数字文本后的尾随垃圾可以吗?
如果溢出一个问题?
如果下溢问题?

然后需要更多代码。根据需要进行调整。

bool isFloatString_Picky(const char *s) {
  char *endptr;
  errno = 0;
  float f = strtof(s, &endptr);
  if (s == endptr) return false; // no conversion

  if (isspace((unsigned char) *s)) return false; // reject leading white-space
  if (*endptr) return false; // reject junk after numeric text

  if (errno) {
    if (fabsf(f) > 1.0f) return false; // reject on overflow
    // yet pass on underflow.
  }
  return true;
}

【讨论】:

  • 这一切都很好,直到 QApplication(或其他任何人,真的)做了一个 setlocale("", LC_ALL) 并且现在 strtod 期望 , 作为小数分隔符。现在您不能再使用此代码来解析例如JSON。
  • @MatteoItalia 也许吧。 OP 的*s == '.' 意味着一个必需的',',在这种情况下,应该为匹配的'.' 确保语言环境。 OP 可能对语言环境调整代码感兴趣,在这种情况下,strtof() 符合这一要求。 OP 的顶级目标中还有很多未指定的内容。顺便说一句,我认为您的意思是 setlocale(LC_ALL, "")
猜你喜欢
  • 2016-06-18
  • 2012-09-10
  • 2017-06-14
  • 2014-01-23
  • 2018-03-20
  • 2021-01-28
  • 2020-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多