预除分母时数值不稳定的风险是什么?
What is the risk of numerical instabilities when predividing denominators?
假设我想把一个数分成很多。
a /= x;
b /= x;
c /= x;
...
因为乘法更快,所以很想这样做
tmp = 1.0f / x;
a *= tmp;
b *= tmp;
c *= tmp;
...
1) 这能保证产生相同的答案吗?我怀疑不是,但一些确认会很好。
2) 如果 x
非常大或非常小,我预计这可能会导致准确性的显着损失。有没有一个公式可以告诉我我会牺牲多少准确性?
3) 也许没有方便的公式,但我们至少可以说明一个经验法则,说明何时会出现数值不稳定性问题?是操作数大小的关系,还是操作数大小的差异?
我的 50 美分:
1) 否,在 3 处进一步解释。
2) 我不知道任何公式,所以我将跳过这个。
3) 我知道的经验法则是尽量只在数量级相近的操作数之间执行运算。
实际样本:
您想将数字 63.000.000 除以 1.000.000。
使用第一种方法,您最终会将 63*10^6 除以 1*10^6,它们的量级非常接近。
但是,如果您使用第二种方法,那么
temp = 1.0f / x;
将产生 10^(-6)。
现在乘以63*10^6 * 10^(-6)会导致明显的精度损失,因为两者之间的量级差异很大。 CPU会尝试用10^6数的指数+分数表示来表示10^(-6)数...
一个可行的替代方案是将 temp 设为
temp = 1 / 1.000;
然后
a = a * temp * temp ;
因为星等会更近,精度损失的可能性更小。
当您使用 ieee-754 编号时,我想说您的方法是完全可以接受的。在 ieee-754 中 x
大致是
mantisa * 2 exponent
其中尾数是 1/2 到 1 之间的数字。
所以只要你只做乘法和除法,你当然会有精度损失,但这种损失与 x
(*) 的大小无关,只与使用的浮点类型的精度(单精度、四精度的双精度,这意味着 float
、double
或 long double
,具体取决于编译器和体系结构)。
(*) 只有当你不会有下溢的溢出时,这才是正确的,对于 10300[ 的单精度,大约 1038 =29=] 用于双精度。
参考资料:维基百科上的第 Floating point and IEEE floating point 页
以下是一些带有支持链接的想法:
1) - 产生相同的结果?没有保证。对可变性的贡献太多了,一切都来自uP设计(记住math co-processor design error of the 486 DX math co-processor?) to compiler implementation, to the way a float is stored in hardware memory. (Good discussion on that here.)
2) - 公式?我不知道一个。而且,显着 错误是什么意思?在任何情况下,您都可以通过以下方式建立对您将看到的精度的期望:
- Understanding various implementations of floating point numbers (link compares 2)
- What variable type is used (
float
, double
, long double
). (differences)
- What architecture are you building on a
32 bit, or 64 bit, other?
关于浮点错误的讨论很多。 Here is one
3) 没有 真实的 经验法则(如果 经验法则你的意思是容易记住,容易应用,简单易懂)但是,这里是a good attempt 在回答有关浮点错误的问题时
1) 不,不保证产生相同的答案。即使使用 IEEE,通过使用 a/x
或 a*(1/x)
.
,细微的舍入效果也可能导致 1 或 2 ULP 的不同
2) 如果 x
非常小(即比 DBL_MIN
(最小归一化正浮点数)小一点,在次正规情况下),1/x
是 INF
,完全失去精度。 x
非常大时也会发生潜在的显着精度损失,例如当 FP 模型不支持次法线时。
通过针对最大的有限数 <= 1/DBL_MIN
和最小的非零数 >= 1/DBL_MAX
测试 |x|
,代码可以确定何时开始出现明显的精度损失。公式可能取决于所使用的 FP 模型和 x
的指数以及模型的限制。在 binary64 的这个范围内,x
和 Emin
(或 Emax
)的二进制指数的差异将是丢失的比特的一阶估计值。
3) 在上面讨论的范围内出现了显着的数值不稳定性。
假设我想把一个数分成很多。
a /= x;
b /= x;
c /= x;
...
因为乘法更快,所以很想这样做
tmp = 1.0f / x;
a *= tmp;
b *= tmp;
c *= tmp;
...
1) 这能保证产生相同的答案吗?我怀疑不是,但一些确认会很好。
2) 如果 x
非常大或非常小,我预计这可能会导致准确性的显着损失。有没有一个公式可以告诉我我会牺牲多少准确性?
3) 也许没有方便的公式,但我们至少可以说明一个经验法则,说明何时会出现数值不稳定性问题?是操作数大小的关系,还是操作数大小的差异?
我的 50 美分:
1) 否,在 3 处进一步解释。
2) 我不知道任何公式,所以我将跳过这个。
3) 我知道的经验法则是尽量只在数量级相近的操作数之间执行运算。
实际样本:
您想将数字 63.000.000 除以 1.000.000。
使用第一种方法,您最终会将 63*10^6 除以 1*10^6,它们的量级非常接近。
但是,如果您使用第二种方法,那么
temp = 1.0f / x;
将产生 10^(-6)。
现在乘以63*10^6 * 10^(-6)会导致明显的精度损失,因为两者之间的量级差异很大。 CPU会尝试用10^6数的指数+分数表示来表示10^(-6)数...
一个可行的替代方案是将 temp 设为
temp = 1 / 1.000;
然后
a = a * temp * temp ;
因为星等会更近,精度损失的可能性更小。
当您使用 ieee-754 编号时,我想说您的方法是完全可以接受的。在 ieee-754 中 x
大致是
mantisa * 2 exponent
其中尾数是 1/2 到 1 之间的数字。
所以只要你只做乘法和除法,你当然会有精度损失,但这种损失与 x
(*) 的大小无关,只与使用的浮点类型的精度(单精度、四精度的双精度,这意味着 float
、double
或 long double
,具体取决于编译器和体系结构)。
(*) 只有当你不会有下溢的溢出时,这才是正确的,对于 10300[ 的单精度,大约 1038 =29=] 用于双精度。
参考资料:维基百科上的第 Floating point and IEEE floating point 页
以下是一些带有支持链接的想法:
1) - 产生相同的结果?没有保证。对可变性的贡献太多了,一切都来自uP设计(记住math co-processor design error of the 486 DX math co-processor?) to compiler implementation, to the way a float is stored in hardware memory. (Good discussion on that here.)
2) - 公式?我不知道一个。而且,显着 错误是什么意思?在任何情况下,您都可以通过以下方式建立对您将看到的精度的期望:
- Understanding various implementations of floating point numbers (link compares 2)
- What variable type is used (
float
,double
,long double
). (differences)- What architecture are you building on a 32 bit, or 64 bit, other?
关于浮点错误的讨论很多。 Here is one
3) 没有 真实的 经验法则(如果 经验法则你的意思是容易记住,容易应用,简单易懂)但是,这里是a good attempt 在回答有关浮点错误的问题时
1) 不,不保证产生相同的答案。即使使用 IEEE,通过使用 a/x
或 a*(1/x)
.
2) 如果 x
非常小(即比 DBL_MIN
(最小归一化正浮点数)小一点,在次正规情况下),1/x
是 INF
,完全失去精度。 x
非常大时也会发生潜在的显着精度损失,例如当 FP 模型不支持次法线时。
通过针对最大的有限数 <= 1/DBL_MIN
和最小的非零数 >= 1/DBL_MAX
测试 |x|
,代码可以确定何时开始出现明显的精度损失。公式可能取决于所使用的 FP 模型和 x
的指数以及模型的限制。在 binary64 的这个范围内,x
和 Emin
(或 Emax
)的二进制指数的差异将是丢失的比特的一阶估计值。
3) 在上面讨论的范围内出现了显着的数值不稳定性。