【问题标题】:How can we check if an input string is a valid double?我们如何检查输入字符串是否是有效的双精度?
【发布时间】:2020-04-14 14:45:55
【问题描述】:

如果我正在从标准输入读取 double 类型的数字,我如何检查正在读取的数字是否确实有效(这些数字实际上是双精度)?

【问题讨论】:

  • 如何定义“不加倍”?
  • 相对于...字母?
  • 吉姆,字母“e”怎么样?

标签: c


【解决方案1】:

您可以使用strtod。根据手册页检查结果是否为零,然后检查是否为endptr == nptr

如果不执行转换,则返回零,并将 nptr 的值存储在 endptr 引用的位置。

类似这样的:

char input[50];
char * end;
double result = 0;

fgets(input, sizeof input, stdin);

errno = 0;

result = strtod(input, &end);

if(result == 0 && (errno != 0 || end == input)){
    fprintf(stderr, "Error: input is not a valid double\n");
    exit(EXIT_FAILURE);
}

EDIT the standard 和手册页之间似乎存在一些差异。手册页说endptr == nptr 当没有执行转换时,而标准似乎暗示这不一定是这种情况。更糟糕的是,如果没有转换,errno可以设置为EINVAL。编辑示例代码以检查errno

或者,sscanf 可以与fgets 一起使用(优先于scanf):

/* just fgetsed input */
if(sscanf(input, "%lf", &result) != 1){
    fprintf(stderr, "Error: input is not a valid double\n");
    exit(EXIT_FAILURE);
}

另外,不要忘记检查fgets 的返回值是否为NULL,以防失败!

【讨论】:

  • “虽然标准似乎暗示不一定是这种情况。” --> C11 §7.22.1.3 7 : "如果主题序列为空或不具有预期形式,则不进行转换;nptr 的值存储在 endptr 指向的对象中,前提是 endptr 不是空指针。” endptr == nptr 足以证明没有转化。
【解决方案2】:

简单的strtodsscanf 都不足以区分1,51blah 等情况与所需的1.0 - 所有这些都会导致1.0。原因是

strtod()strtof()strtold() 函数将 nptr 指向的字符串的初始部分分别转换为 double、float 和 long double 表示形式。

要确保 整个 字符串是有效的双精度字面值,请像这样使用 strtod

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>

...

char *endptr;
errno = 0;
double result = strtod(input, &endptr);
if (errno != 0 || *endptr != '\0') {
    fprintf(stderr, "the value could not be represented as a double exactly\n");
}

如果值无法表示 (ERANGE),将设置 errno。此外,end 将指向未转换的第一个字符。如果区域设置尚未设置,则在解析1,51blah 时,endptr 将指向 字符。 如果整个字符串被成功解析为双精度常量,*endptr 将指向终止的'\0'

注意errno必须在调用函数之前设置为零,否则它将保留先前失败的函数调用的值。

【讨论】:

  • input[0] == 0, errno != 0 || *endptr != '\0' 仍然为false,因为errno 可能保持为0。同时建议fprintf(stderr, ...
【解决方案3】:

我们如何检查输入字符串是否是有效的双精度字符串?


strtod() 开始,double
strtof()float
strtold()long double

double strtod(const char * restrict nptr, char ** restrict endptr);

strtod, ... 函数将nptr 指向的字符串的初始部分转换为double ....

指向最终字符串的指针存储在endptr 指向的对象中,前提是endptr 不是空指针。

C11dr §7.22.1.3 2&5

简化的代码可以轻松检查有效性。不会抱怨上溢/下溢或多余的文字。

// Return true on valid
bool valid_string_to_double(const char *s) {
  char *end;
  strtod(s, &end);
  return s != end;
}

使用strto*() 的挑战包括:errno == RANGE 算术溢出和可能underflow。溢出时的返回值仅在默认舍入模式下指定。该值是HUGE_VAL,它可能是无穷大或很大的数字。下溢的返回值是实现定义的。 errno 已知会在 C 规范未指定的条件下设置为其他非零值。允许前导空格,不考虑尾随空格。


查找 1) 转换、2) 额外空间、3) 上溢/下溢的示例函数。它不仅返回一个有效的指示,它还处理转换的值和之后errno 的状态。

// Return 0 on success
// Return non-0 on error, adjust these values as needed - maybe as an `enum`?
int convert_string_to_double(double *y, const char *s) {
  char *end;
  errno = 0;
  *y = strtod(s, &end);

  if (s == end) {
    return 1; // Failed: No conversion, *y will be 0
  }

  // This may/may not constitute an error - adjust per coding goals
  // Too great or too small (yet not exactly 0.0)
  if (errno == ERANGE) {
    if (fabs(*y) > 1.0) {
      return 2; // Overflow
    }

    // In the case of too small, errno _may_ be set. See §7.22.1.3 10.
    // For high consistency, return 0.0 and/or clear errno and/or return success.
    // *y = 0.0; errno = 0; 
  }

  // What to do if the remainder of the string is not \0?
  // Since leading whitespace is allowed, 
  // let code be generous and tolerate trailing whitespace too.
  while (isspace((unsigned char) *end)) {
    end++;
  }
  if (*end) {
    return 3; // Failed: Extra non-white-space junk at the end.
  }

  return 0;  // success
}

如果结果下溢 (7.12.1),则函数返回一个值,其大小不大于返回类型中的最小归一化正数; errno 是否获取值ERANGE 是实现定义的。 C11dr §7.22.1.3 10


在此函数完成后,考虑包括errno 的值。 C 规范仅将errno == ERANGE 用于strtod(),但已知各种实现将errno 设置为其他值,包括“无转换”等其他原因。代码可以清除errno,除非ERANGE 具有高一致性。

【讨论】:

    【解决方案4】:

    您可以使用标准的 atof 函数。它在失败时返回 0 - 您可以预先测试字符串是否为 0。

    http://www.cplusplus.com/reference/cstdlib/atof/

    【讨论】:

    猜你喜欢
    • 2015-07-30
    • 1970-01-01
    • 1970-01-01
    • 2019-07-18
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 2020-10-16
    相关资源
    最近更新 更多