需要注意的一件主要事情是 C 语言最初指定了类似的计算
float a=b+c+d;
将 b、c 和 d 转换为可用的最长浮点类型(恰好是 double 类型),将它们相加,然后将结果转换为 float。这种语义对编译器来说很简单,对程序员也有帮助,但有一点困难:存储数字的最有效格式与执行计算的最有效格式不同。在没有浮点硬件的机器上,对存储为不必要归一化的 64 位尾数和单独存储的 15 位指数和符号的值执行计算,然后对存储为 64 位的值进行运算会更快。位 double 必须在每次操作之前解包,然后规范化并在之后重新打包(即使只为下一次操作立即解包)。让机器以较长格式保存中间结果,提高了速度和准确性; ANSI C 允许使用 long double 类型。
不幸的是,ANSI C 未能提供一种方法,通过该方法,变量参数函数可以指示他们是否希望将所有浮点值转换为 long double,全部转换为 double,或者具有 float 和 @ 987654329@ 作为 double 传递,long double 作为 long double 传递。如果存在这样的设施,那么编写不必区分double 和long double 值的代码会很容易。不幸的是,缺少这样的功能意味着在double 和long double 是不同类型的系统上,代码确实必须关心区别,而在它们不是的系统上则不需要。这反过来意味着在类型相同的系统上编写的大量代码会在类型不同的系统上中断。编译器供应商认为最简单的解决方法是简单地将 long double 与 double 同义,并且不提供任何可以准确保存中间计算的类型。
由于以不可表示的类型执行中间计算是不好的,一些人认为合乎逻辑的事情是将float 上的计算作为float 类型执行。虽然在某些硬件平台上这可能比使用 double 类型更快,但它通常会对准确性产生不良后果。考虑:
float triangleArea(float a, float b, float c)
{
long double s = (a+b+c)/2.0;
return sqrt((s-a)*(s-b)*(s-c)*c);
}
在使用long double 执行中间计算的系统上,这将产生良好的准确性。在以float 执行中间计算的系统上,即使 a、b 和 c 都可以精确表示,这可能会产生可怕的准确性。例如,如果 a 和 b 为 16777215.0f,c 为 4.0f,则s 的值应为 16777217.0,但如果 a、b 和 c 之和计算为float,则为 1677216.0;这将产生一个小于一半正确值的区域。如果 a 和 c 是 16777215.0f 而 b 是 4.0f(相同的数字;不同的顺序),那么 s 将被计算为 16777218.0,产生一个 50% 太大的区域。
如果您的计算在 x86 上产生了良好的结果(许多编译器急切地升级为 80 位类型,即使它们无益地使其对程序员不可用)但在 x64 上的结果很糟糕,我猜你可能有像上面这样的计算需要以比操作数或最终结果更高的精度执行中间步骤。将上述方法的第一行改为:
long double s = ((long double)a+b+c)/2.0;
将强制以更高精度完成中间计算,而不是以低精度执行计算,然后将不准确的结果存储到更高精度的变量中。