在处理浮点值时,我应该结合乘法和除法步骤吗?

时间:2014-10-30 14:41:20

标签: c++ visual-c++ floating-point floating-point-precision

我知道花车和双打中的精度问题,这就是为什么我这样问:

如果我有一个公式,例如:(a/PI)*180.0(其中PI是常数)

我应该将除法和乘法结合起来,所以我只能使用一个除法:a/0.017453292519943295769236,以避免精度损失?

如果计算结果的步骤较少,这是否会使其更精确?

1 个答案:

答案 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的准确度。