使用浮点值时,我应该将乘法和除法步骤结合起来吗
Should I combine multiplication and division steps when working with floating point values?
我知道浮点和双精度的问题,这就是我问这个问题的原因:
如果我有一个公式,比如:(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的精度差得多。
- std::vector的包装器,使数组的结构看起来像结构的数组
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- 看起来is_nothrow_constructible_v()在MSVC中被破坏了,我错了吗
- 在除法中不需要四舍五入
- 将大括号括起来的初始值设定项列表作为结构返回
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 在TFHE(完全快速同态加密)上执行除法
- 使用 int 表示浮点除法 C++
- 将 SDL2 与 CMake 链接起来
- 调用重载的"<大括号括起来的初始值设定项列表>"对于对来说就足够了是模棱两可的
- 而循环:简单的除法程序输出零,不明白为什么
- 如何使用connect将qml按钮与同一类的cpp函数连接起来
- 尽管一切看起来都很好,但值不会交换
- 尝试将 c 字符串数组与分隔符连接起来
- 余数除法和不允许除以零 (c++) 时遇到问题
- 为什么我会收到此错误?无法将 {lb, ub} 从<大括号括起来的初始值设定项列表>转换为 float(**)(float*, int)
- 有没有办法生成一个包含平方的序列,这些平方加起来就是一个整数平方?
- 计算数组重复次数的组合的有效算法,加起来达到给定的总和
- 使用浮点值时,我应该将乘法和除法步骤结合起来吗
- 为什么我的除法结果看起来是错的