比较从字符串转换的浮点值与文字

时间:2017-10-18 12:43:27

标签: c++ c floating-point language-lawyer

这不是着名的Is floating point math broken的副本,即使它看起来像乍一看。

我正在使用double从文本文件中读取fscanf(file, "%lf", &value);,并将其与==运算符对照双字面值进行比较。如果字符串与文字相同,那么在所有情况下使用==的比较是true吗?

示例

文字文件内容:

7.7

代码段:

double value;
fscanf(file, "%lf", &value);     // reading "7.7" from file into value

if (value == 7.7)
   printf("strictly equal\n");

预期和实际产出

strictly equal

但是这假设编译器将双文本7.7转换为与fscanf函数完全相同的双精度,但编译器可能会也可能不会使用相同的库将字符串转换为双

或者另有要求:从字符串到double的转换是否会产生唯一的二进制表示形式,或者可能存在轻微的实现依赖差异?

Live demonstration

4 个答案:

答案 0 :(得分:18)

来自c ++标准:

  

[lex.fcon]

     

...如果缩放值在范围内   对于其类型的可表示值,结果是可表示的缩放值,否则更大或更小   最接近缩放值的可表示值,以实现定义的方式选择 ...

强调我的。

因此,如果值可以用double严格表示,那么你只能依赖于相等。

答案 1 :(得分:17)

关于C ++,from cppreference one can read

[lex.fcon] (§6.4.4.2)

  

评估浮点常量的结果是最接近的可表示值或紧邻最近的可表示值的较大或较小的可表示值,以实现定义的方式选择(换句话说,转换期间的默认舍入方向是实现-defined)。

由于浮动文字的表示未指定,我猜你无法总结它与scanf结果的比较。

关于C11(标准ISO / IEC 9899:2011):

[lex.fcon] (§6.4.4.2)

  

推荐做法

     

7浮动常量的转换时间转换应与库函数(如strtod)的字符串执行时转换相匹配,给定适合两种转换的匹配输入,相同的结果格式和默认执行-时间   四舍五入。

对C11来说很明显,这不能保证匹配。

答案 2 :(得分:2)

  

如果字符串与文字相同,那么使用==的比较在所有情况下都是真的吗?

尚未探讨的共同考虑因素:FLT_EVAL_METHOD

#include <float.h>
...
printf("%d\n", FLT_EVAL_METHOD);
  

2评估所有操作和常数的范围和精度   long double类型。

如果返回2,则value == 7.7中使用的数学运算为long double7.7处理为7.7L。在OP的情况下,这可能会被评估为假。

要考虑更宽的精度,请指定将删除所有额外范围和精度的值。

scanf(file, "%lf", &value);
double seven_seven = 7.7;
if (value == seven_seven)
  printf("strictly equal\n");

IMO,这是一个比变体舍入模式或库/编译器转换的变化更可能出现的问题。

请注意,这种情况类似于下面这个众所周知的问题。

float value;
fscanf(file, "%f", &value);
if (value == 7.7)
   printf("strictly equal\n");

示范

#include <stdio.h>
#include <float.h>
int main() {
  printf("%d\n", FLT_EVAL_METHOD);
  double value;
  sscanf("7.7", "%lf", &value);
  double seven_seven = 7.7;
  if (value == seven_seven) {
    printf("value == seven_seven\n");
  } else {
    printf("value != seven_seven\n");
  }
  if (value == 7.7) {
    printf("value == 7.7\n");
  } else {
    printf("value != 7.7\n");
  }
  return 0;
}

输出

2
value == seven_seven
value != 7.7

替代比较

比较&#34;近&#34;的2 double彼此之间,我们需要定义&#34; near&#34;。一种有用的方法是考虑排序为升序的所有有限double值,然后将它们的序列号相互比较。 double_distance(x, nextafter(x, 2*x) - &gt; 1

以下代码对double布局和大小做出了各种假设。

#include <assert.h>

unsigned long long double_order(double x) {
  union {
    double d;
    unsigned long long ull;
  } u;
  assert(sizeof(double) == sizeof(unsigned long long));
  u.d = x;
  if (u.ull & 0x8000000000000000) {
    u.ull ^= 0x8000000000000000;
    return 0x8000000000000000 - u.ull;
  }
  return u.ull + 0x8000000000000000;
}

unsigned long long double_distance(double x, double y) {
  unsigned long long ullx = double_order(x);
  unsigned long long ully = double_order(y);
  if (x > y) return ullx - ully;
  return ully - ullx;
}

....
printf("%llu\n", double_distance(value, 7.7));                       // 0
printf("%llu\n", double_distance(value, nextafter(value,value*2)));  // 1
printf("%llu\n", double_distance(value, nextafter(value,value/2)));  // 1

或者只是使用

if (nextafter(7.7, -INF) <= value && value <= nextafter(7.7, +INF)) {
  puts("Close enough");
}

答案 3 :(得分:2)

没有保证。

您可以希望编译器使用高质量的算法来转换文字,并且标准库实现也使用高质量的转换,并且两个高质量的算法应该经常达成一致。

它们也可能使用完全相同的算法(例如,编译器通过将字符放入char数组并调用sscanf来转换文字。

顺便说一句。我有一个错误,因为编译器没有完全转换文字999999999.5。用9999999995 / 10.0替换它,一切都很好。