我知道花车和双打中的精度问题,这就是为什么我这样问:
如果我有一个公式,例如:(a/PI)*180.0
(其中PI是常数)
我应该将除法和乘法结合起来,所以我只能使用一个除法:a/0.017453292519943295769236
,以避免精度损失?
如果计算结果的步骤较少,这是否会使其更精确?
答案 0 :(得分:4)
是的,通常应该将尽可能多的乘法和除法组合成一个操作。它(通常(*))同时更快,更准确。
π和π/ 180及其反转都不能完全表示为浮点。因此,计算将涉及至少一个近似常数(除了所涉及的每个操作的近似值之外)。
因为两个操作分别引入了一个近似值,所以可以预期在一次操作中进行整个计算会更准确。
除此之外,“幸运”的问题是π/ 180在浮点格式中表示的相对精度是好于还是差于180 /π。
我的编译器提供了long double
类型的附加精度,因此我可以将其用作double
回答此问题的参考:
~ $ cat t.c
#define PIL 3.141592653589793238462643383279502884197L
#include <stdio.h>
int main() {
long double heop = 180.L / PIL;
long double pohe = PIL / 180.L;
printf("relative acc. of π/180: %Le\n", (pohe - (double) pohe) / pohe);
printf("relative acc. of 180/π: %Le\n", (heop - (double) heop) / heop);
}
~ $ gcc t.c && ./a.out
relative acc. of π/180: 1.688893e-17
relative acc. of 180/π: -3.469703e-17
在通常的编程实践中,人们不会打扰并简单地乘以(180 /π的浮点表示),因为乘法比除法快得多。
事实证明,在binary64浮点类型double
几乎总是映射到的情况下,π/ 180可以用比180 /π更好的相对精度来表示,所以π/ 180是应该使用的常数优化准确度:a / ((double) (π / 180))
。使用这个公式,总相对误差大约是常数的相对误差(1.688893e-17)和除法的相对误差之和(取决于a
的值,但永远不会是超过2 -53 )。
请注意,划分是如此昂贵,以至于通过使用一个乘法和一个fma可以更快地获得更准确的结果:让heop1
成为180 /π的最佳double
近似值,{{{ 1}}最佳heop2
近似180 /π - double
。然后,结果的最佳值可以计算为:
heop1
以上是对实数计算的绝对最佳double r = fma(a, heop1, a * heop2);
近似的事实是一个定理(事实上,它是一个有例外的定理。细节可以在“浮点手册”中找到算术”)。但即使你想要乘以double
以获得double
结果的实常数是该定理的例外之一,上述计算仍然非常准确,只是与double
的一些特殊值的最佳double
近似值。
如果像我一样,你的编译器为a
提供的精度比long double
更高,你也可以使用一个double
乘法:
long double
这不如基于fma的解决方案好,但对于// this is more accurate than double division:
double r = (double)((long double) a * 57.295779513082320876798L)
的大多数值来说,它足以产生对实际计算的最佳a
近似值。
(*)对大多数常数来说,最好将常数分组是合理的。
如果您希望将double
乘以(例如)实常数0.0000001 * a
,那么最好先乘以DBL_MIN
,再乘以{{1}并且最终结果(如果0.0000001
大于1000000左右,则可以是标准化数字)将比您乘以0.0000001 * {{1的最佳DBL_MIN
表示更精确}}。这是因为将0.0000001 * a
表示为单个double
值时的相对准确度远远低于表示0.0000001的准确度。