需要注意的一件主要事情是 C 语言最初指定计算
float a=b+c+d;
将 b、c 和 d 转换为最长的可用浮点类型(恰好是 type double
),将它们相加,然后将结果转换为float
. 这样的语义对编译器来说很简单,对程序员有帮助,但有一点困难:存储数字的最有效格式与执行计算的最有效格式不同。在没有浮点硬件的机器上,对存储为不必要归一化的 64 位尾数和单独存储的 15 位指数和符号的值执行计算,然后对存储为 64 位的值进行运算会更快。少量double
必须在每次操作之前解包,然后规范化并在之后重新打包(即使只是为下一次操作立即解包)。让机器以较长格式保存中间结果,提高了速度和准确性;ANSI C 允许使用 type 进行此操作long double
。
不幸的是,ANSI C 未能提供一种方法,通过该方法,变量参数函数可以指示它们是否希望将所有浮点值转换为long double
、全部转换为double
,或者将float
其double
传递为double
和long double
as long double
。如果存在这样的设施,那么编写不必区分值double
和long double
值的代码会很容易。不幸的是,缺少这样的功能意味着在系统上,double
并且long double
是不同类型的代码确实必须关心区别,而在它们不是的系统上则不需要。这反过来意味着在类型相同的系统上编写的大量代码会在类型不同的系统上中断。编译器供应商决定最简单的解决方法是简单地使其long double
成为同义词,double
而不提供任何可以准确保存中间计算的类型。
由于以不可表示的类型执行中间计算是不好的,因此有些人认为合乎逻辑的事情是将计算float
作为 type 执行float
。虽然在某些硬件平台上这可能比使用 type 更快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
,这将产生良好的准确性。在执行中间计算的系统上,即使 a、b 和 c 都可以精确表示float
,这也可能产生可怕的准确性。例如,如果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;
将强制以更高精度完成中间计算,而不是以低精度执行计算,然后将不准确的结果存储到更高精度的变量中。