使用浮点值时,我应该将乘法和除法步骤结合起来吗

Should I combine multiplication and division steps when working with floating point values?

本文关键字:除法 起来 结合 我应该      更新时间:2023-10-16

我知道浮点和双精度的问题,这就是我问这个问题的原因:

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

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

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

简短回答

是的,通常应该将尽可能多的常数乘法和除法组合成一个运算。它(通常(*))更快,同时也更准确。

π、π/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: %Len", (pohe - (double) pohe) / pohe);
  printf("relative acc. of 180/π: %Len", (heop - (double) heop) / heop);
}
~ $ gcc t.c && ./a.out 
relative acc. of π/180: 1.688893e-17
relative acc. of 180/π: -3.469703e-17

在通常的编程实践中,人们不会麻烦,只需乘以(的浮点表示)180/π,因为乘法比除法快得多。事实证明,在二进制64浮点类型double几乎总是映射到的情况下,π/180可以用比180/π更好的相对精度来表示,因此π/180是用于优化精度的常数:a / ((double) (π / 180))。利用这个公式,总相对误差将近似为常数的相对误差(1.688893e-17)和除法的相对误差的和(这将取决于a的值,但永远不会超过2-53)。

获得更快、更准确结果的替代方法

请注意,除法非常昂贵,因此使用一次乘法和一次fma可以更快地获得更准确的结果:设heop1是180/π的最佳double近似值,heop2是180/π的最好double近似值-heop1。然后,结果的最佳值可以计算为:

double r = fma(a, heop1, a * heop2);

事实上,上述是对实际计算的绝对最佳可能double近似是一个定理(事实上,这是一个例外的定理。详细信息可在"浮点运算手册"中找到)。但是,即使要将double乘以以获得double结果的实常数是该定理的例外之一,上述计算显然仍然非常准确,并且仅与a的少数例外值的最佳double近似不同。


如果像我一样,您的编译器为long double提供的精度比为double提供的精度高,那么您也可以使用一个long double乘法:

// this is more accurate than double division:
double r = (double)((long double) a * 57.295779513082320876798L)

这不如基于fma的解好,但它足够好,对于a的大多数值,它产生了对实际计算的最佳double近似。

与一般主张的操作应分组为一个相反的例子

(*)将常数分组更好的说法仅在统计上适用于大多数常数。

如果你碰巧想用a乘以,比如说,实数常数0.0000001*DBL_MIN,你最好先乘以0.0000001,然后乘以DBL_MIN,最终结果(如果a大于1000000左右,它可以是一个归一化的数字)将比乘以0.0000001*DBL_MIN的最佳double表示更精确。这是因为当将0.0000001*DBL_MIN表示为单个double值时的相对精度比表示0.0000001的精度差得多。