1

这段小代码让我抓狂:

#include <stdio.h>

    int main() 
{
double x;
const double d=0.1;
x=d ;
for (int i=0; i<30; i++) 
    {
    printf("Cycle %d  Value :%.20e \n",i,x);
    x=x*(double)11.-(double)10*d; //11*0.1 = 1.1 - 10*0.1 = 1 => 0.1

    }       
return 0;
}

事实上,由于 IEEE 754 标准中浮点数的内部表示,我试图证明一个病态的案例。在 MacOs 或 windows 机器上,最终输出行将显示为:

周期 29 值:1.28084153156127500000e+13

但是在 Linux (Scientific Linux 5.4) 上,代码将毫无问题地运行。阅读我发现:

在 FreeBSD、NetBSD 和 OpenBSD 等 BSD 系统上,硬件双精度舍入模式是默认的,最大程度地兼容原生双精度平台。在 x86 GNU/Linux 系统上,默认模式是扩展精度(旨在提供更高的精度)。

在同一页上解释了GCC INTRO如何在 Linux 系统上启用双精度舍入,而不是如何在其他系统上使用扩展精度。这在 MacO 或 Windows 上可行吗?如何 ?

4

1 回答 1

3

在 OS X 上简单地使用扩展精度很容易:

x=11.L*x - 10.L*d;

L后缀导致两个文字为long doubles 而不是s double,这会强制根据 C 的表达式评估规则以 80 位扩展来评估整个表达式。

除此之外,您的问题似乎有些混乱;你说“......在 Linux 上,代码将毫无问题地运行。” 几点:

  • OS X 结果和 Linux 结果均符合 IEEE-754 和 C 标准。其中任何一个都没有“问题”。
  • OS X 结果可在不支持(非标准)80 位浮点类型的硬件上重现。Linux 结果不是。
  • 依赖于 80 位扩展的中间结果的计算是脆弱的;更改编译器选项、优化设置甚至程序流程都可能导致结果发生变化。OS X 结果将在这些更改中保持稳定。

最后,您必须记住浮点运算不是真正的运算。在 Linux 上获得的结果更接近用实数评估表达式时获得的结果这一事实并没有使这种方法更好(或更差)。

对于每一种自动使用扩展精度节省了浮点数的天真的用户的情况,我都可以向您展示该评估模式的不可预测性引入了一个微妙且难以诊断的错误的情况。这些通常被称为“超精度”错误;最近最著名的例子之一是允许用户2.2250738585072011e-308输入 Web 表单并导致服务器崩溃的错误。最终的原因恰恰是编译器在程序员的背后,并保持比它被指示的更高的精度。OS X 不受此错误的影响,因为双精度表达式是以双精度计算的,而不是扩展的。

只要系统具有可重现性和可移植性,人们就可以了解浮点运算的陷阱。以双精度和单精度计算双精度表达式提供了这些属性。使用扩展精度评估会破坏它们。在您的工具不可预测的环境中,您无法进行严肃的工程。

于 2011-10-07T12:46:39.957 回答