C基础:双精度变量不等于双精度表达式?

8

我正在处理一个名为indata的double数组(在堆中,用malloc分配),还有一个名为sum的本地double变量。

我编写了两个不同的函数来比较indata中的值,并获得了不同的结果。最终我确定差异是由于一个函数在条件测试中使用了一个表达式,而另一个函数在同一条件测试中使用了一个本地变量。我希望它们是等价的。

我的函数A使用:

    if (indata[i]+indata[j] > max) hi++;

我的函数B使用:

    sum = indata[i]+indata[j];
    if (sum>max) hi++;

使用相同的数据集和max后,根据所使用的函数不同,我得到了不同的hi值。我认为函数B是正确的,而函数A是误导性的。类似地,当我尝试下面的代码片段时:

    sum = indata[i]+indata[j];
    if ((indata[i]+indata[j]) != sum) etc.

这个条件语句会评估为真。

虽然我知道浮点数不一定提供精确的表示,但为什么当作为表达式进行评估时,这种非准确的表示会改变,而在变量中存储时却不会?建议最佳实践是在条件之前始终评估双精度表达式吗?谢谢!


这基本上是因为计算机无法完全精确地表示数字。请阅读有关浮点数的内容。 - Iharob Al Asimi
@iharob 他在最后一段承认了这一点。但是这并不能解释为什么根据是否将结果分配给变量会有所不同。 - Barmar
2
当赋值发生在B中时,需要将值四舍五入到最接近的“double”值(通常为64位)。在函数A中,条件表达式可能使用更高的精度进行评估(例如80位)。 - user3386109
1
如果代码确实是为 x86 而不是 x86-64 编译的,因此使用的是 x87 而不是 SSE 浮点指令,那么这确实是最有可能的解释。 - Dolda2000
2
请查看http://www.exploringbinary.com/when-doubles-dont-behave-like-doubles/。 - Rick Regan
显示剩余5条评论
1个回答

11

我怀疑您正在使用32位x86,这是唯一一个受到过度精度影响的常见架构。在C语言中,类型为floatdouble的表达式实际上被评估为float_tdouble_t,它们与floatdouble之间的关系反映在FLT_EVAL_METHOD宏中。在x86的情况下,两者都定义为long double,因为fpu实际上无法执行单精度或双精度算术运算(它有模式位以允许这样做,但行为略有不同,因此不能使用)。

将类型为floatdouble的对象赋值是强制舍入并摆脱过度精度的一种方法,但您也可以只添加一个(double)的无意义转换,如果您希望将其保留为表达式而没有分配操作。

请注意,强制将四舍五入到所需精度与在所需精度上执行算术运算不等效;您现在有两个舍入步骤(在计算期间和再次删除不需要的精度),并且在第一次舍入给您一个精确的中点的情况下,第二次舍入可能会朝着“错误”的方向进行。这个问题通常称为双重舍入,它使得在某些类型的计算中过度精度比名义精度显著更糟糕。


感谢您的解释。我正在运行代码,使用的是一台搭载64位Windows 7操作系统的i7-3770 CPU。然而,我的编译器是32位应用程序minGW。我将调查编译器设置。我知道有一种硬件级别的精度高于double,并且会更加小心地使用表达式和变量。顺便说一下,在这种情况下对表达式进行类型转换实际上不起作用 - 与没有转换时的行为相同。 - stilllearning
1
尝试使用-std=c99-fexcess-precision=standard进行转换。在某些不符合标准的模式下,GCC会出现错误的行为。 - R.. GitHub STOP HELPING ICE
谢谢 - 任何一个编译器命令行选项都可以解决问题,使得这段代码正确地运行:if ((double)(indata[i]+indata[j]) > max) hi++; - stilllearning

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