假设浮点数计算可用,原作者的算法是一个很好的选择,始终优于备选方案a + f * (b - a)
,因为当a
和b
的数量级相差较大时,存在精度损失。
例如:
float lint1 (float a, float b, float f) {
return (a * (1.0f - f)) + (b * f);
}
float lint2 (float a, float b, float f) {
return a + f * (b - a);
}
在该示例中,假设使用32位浮点数,lint1(1.0e20, 1.0, 1.0)
将正确返回1.0,而lint2
将错误地返回0.0。
由于操作数在数量级上有显著差异时,大部分精度损失发生在加法和减法运算符中。在上述情况中,罪魁祸首是b - a
中的减法和a + f * (b - a)
中的加法。由于组件在相加之前完全乘以,所以OP的算法不会受到此影响。
对于a = 1e20,b = 1的情况,这是不同结果的示例。测试程序:
#include <stdio.h>
#include <math.h>
float lint1 (float a, float b, float f) {
return (a * (1.0f - f)) + (b * f);
}
float lint2 (float a, float b, float f) {
return a + f * (b - a);
}
int main () {
const float a = 1.0e20;
const float b = 1.0;
int n;
for (n = 0; n <= 1024; ++ n) {
float f = (float)n / 1024.0f;
float p1 = lint1(a, b, f);
float p2 = lint2(a, b, f);
if (p1 != p2) {
printf("%i %.6f %f %f %.6e\n", n, f, p1, p2, p2 - p1);
}
}
return 0;
}
格式略微调整后的输出:
f lint1 lint2 lint2-lint1
0.828125 17187500894208393216 17187499794696765440 -1.099512e+12
0.890625 10937500768952909824 10937499669441282048 -1.099512e+12
0.914062 8593750447104196608 8593749897348382720 -5.497558e+11
0.945312 5468750384476454912 5468749834720641024 -5.497558e+11
0.957031 4296875223552098304 4296874948674191360 -2.748779e+11
0.972656 2734375192238227456 2734374917360320512 -2.748779e+11
0.978516 2148437611776049152 2148437474337095680 -1.374390e+11
0.986328 1367187596119113728 1367187458680160256 -1.374390e+11
0.989258 1074218805888024576 1074218737168547840 -6.871948e+10
0.993164 683593798059556864 683593729340080128 -6.871948e+10
1.000000 1 0 -1.000000e+00
f
的指数为非正数(当然也保证了1
的尾数和指数的固定值),从而削减了减法例程中的大部分分支。 - Sneftelf *(b-a)
的数量级与a
明显不同,则 OP 的方法仍然更精确,因为它就会导致加法出现问题。正如上面所说的,即使使用 OP 的方法,如果f
相对于1.0f
太大,则也可能失败,因为对于非常大的f
,1.0f - f
可能等价于-f
。因此,如果您正在处理巨大的f
值,则需要认真考虑一下数学问题。问题在于,您会遇到这样的情况:1.0 + 1.0e800 == 1.0e800
。 - Jason Ca == b
时的情况:该算法将始终返回正确的答案,但根据t
的值,OP 的算法可能会失去加法左侧和右侧的精度,并且它们不会相加等于初始值。 - nemetroid