【发布时间】:2020-04-14 14:45:55
【问题描述】:
如果我正在从标准输入读取 double 类型的数字,我如何检查正在读取的数字是否确实有效(这些数字实际上是双精度)?
【问题讨论】:
-
如何定义“不加倍”?
-
相对于...字母?
-
吉姆,字母“e”怎么样?
标签: c
如果我正在从标准输入读取 double 类型的数字,我如何检查正在读取的数字是否确实有效(这些数字实际上是双精度)?
【问题讨论】:
标签: c
您可以使用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,以防失败!
【讨论】:
endptr == nptr 足以证明没有转化。
简单的strtod 和sscanf 都不足以区分1,5 或1blah 等情况与所需的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,5 或1blah 时,endptr 将指向秒 字符。 如果整个字符串被成功解析为双精度常量,*endptr 将指向终止的'\0'。
注意errno必须在调用函数之前设置为零,否则它将保留先前失败的函数调用的值。
【讨论】:
input[0] == 0, errno != 0 || *endptr != '\0' 仍然为false,因为errno 可能保持为0。同时建议fprintf(stderr, ...
我们如何检查输入字符串是否是有效的双精度字符串?
从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 具有高一致性。
【讨论】:
您可以使用标准的 atof 函数。它在失败时返回 0 - 您可以预先测试字符串是否为 0。
【讨论】: