首先,我意识到大多数基数为10的数字不能在基数2中精确表示,所以我的问题并不是关于浮点运算的不足。
我正在尝试编写一个函数,该函数将尝试通过检查最后6个有意义的数字是否在某个容差范围内并将其更改为下一个可表示的某个假设的精确值(仅用于显示目的)来修正累积舍入误差的双重污染 - 除非它是整数或2的幂。)
令我惊讶的是我的函数的一个组件是exp10的输出;据我所知,只要两个双精度之间的间距小于2,那么存储为双精度的整数值应该是精确的 - 尽管10 ^ 14正在推动它,这应该是一个精确的整数(自10 ^ 14 = ~2 ^ 46.507 <2 ^ 53)。然而,这不是我的测试所显示的。
我的调试工作的摘录(没有什么是显而易见的)和输出如下:
double test = 0.000699;
double tmp = fabs(test);
double exp = 10.0 - floor(log10(tmp));
double powTen = exp10(10.0 - floor(log10(tmp)));
double powTen2 = exp10(exp);
double powTen3 = exp10((int)exp);
double powTen4 = exp10(exp);
double powTen5 = pow(10, exp);
printf("exp: %.16lf\n", exp);
printf("powTen: %.16lf\n", powTen);
printf("powTen2: %.16lf\n", powTen2);
printf("powTen3: %.16lf\n", powTen3);
printf("powTen4: %.16lf\n", powTen4);
//these two are exact
printf("10^14: %.16lf\n", exp10(14));
printf("powTen5: %.16lf\n", powTen5);
printf("exp == 14.0: %d\n", exp == 14.0);
输出:
exp: 14.0000000000000000
powTen: 100000000000000.1250000000000000
powTen2: 100000000000000.1250000000000000
powTen3: 100000000000000.1250000000000000
powTen4: 100000000000000.1250000000000000
10^14: 100000000000000.0000000000000000
powTen5: 100000000000000.0000000000000000
exp == 14.0: 1
pow得到的答案是精确的,因为exp10带有硬编码的int。对于所有其他情况,我在1/8中添加(10 ^ 14和10 ^ 14之间的间距+下一个可表示的是1/64)。 文档说exp10应该相当于pow。谁能看到我失踪的东西?
编辑 - 使用O3,O2,O1优化我得到预期的输出 - 除非数据在运行时才能知道。在这一点上,exp10仍然行为不端。
答案 0 :(得分:6)
您的exp10
实施可能行为不端。请注意,它返回的结果有时会被一个ulp(相对于你的10 ^ 14的0.125)所取消。
这是一个相当令人发指的错误;您有一个案例,其中正确的答案可以表示为double
但exp10
尚未这样做。
我回应Ben Voigt的观点,即编译器有时可能会自行评估事物而不是将它们传递给数学库。它可能做得更好,因为它可能链接到任意精度的数学库。您可以尝试使用-fno-builtin
选项来查看它是否会发生任何变化。
不幸的是,我不认为crlibm已实施exp10
。否则,我建议您只使用它并停止担心。
编辑:我eglibc
来源的副本似乎实现了exp10
因此:
double
__ieee754_exp10 (double arg)
{
/* This is a very stupid and inprecise implementation. It'll get
replaced sometime (soon?). */
return __ieee754_exp (M_LN10 * arg);
}
不要期望这种方法能够很好地运作。