问题描述
从计算机系统:程序员的角度来看:
具有单精度浮点数
表达式
(3.14f+1e10f)-1e10f
的值为0.0:由于舍入而丢失了值3.14。表达式
(1e20f*1e20f)*1e-20f
的值为+∞,而1e20f*(1e20f*1e-20f)
的表达式为1e20f
。
-
如何检测由于浮点加法和乘法舍入而导致的精度损失?
-
下溢与我描述的问题之间的关系和区别是什么?下溢是否仅是由于舍入而导致精度下降的特殊情况,将结果舍入为零?
谢谢。
解决方法
尽管在数学中,实数的加法和乘法是关联运算,但由于精度和范围有限,在浮点类型(例如float
)上执行时,这些运算不是关联扩展名。
所以顺序很重要。
考虑示例,数字10000000003.14不能完全表示为32位float
,因此(3.14f + 1e10f)
的结果将与{{1 }},是最接近的可表示数字。当然,1e10f
会改为产生3.14f + (1e10f - 1e10f)
。
请注意,我使用了3.14f
后缀,因为在C中,表达式f
包含(3.14+1e10)-1e10
文字,因此结果的确是double
(或更可能是像3.14999)。
在第二个示例中发生了类似的情况,其中3.14
已经超出了1e20f * 1e20f
的范围(但没有超出float
的范围),并且连续的乘法是没有意义的,而{{1}在其他表达式中首先执行的}具有明确的结果(1),并且连续相乘产生正确的答案。
实际上,您采取了一些预防措施
- 使用更广泛的类型。除非有其他要求,否则
double
最适合大多数应用。 - 如果可能,请重新排序操作。例如,如果您必须添加许多术语,并且您知道一些术语小于其他术语,请先添加这些术语,然后再添加其他术语。避免相减相同数量级的数字。通常,评估代数表达式的方法可能比幼稚的方法更为准确(例如,用于多项式评估的霍纳法)。
- 如果您对问题域有某种了解,那么您可能已经知道计算的哪一部分可能存在问题值,并在执行计算之前检查这些值是否大于(或小于)某些限制。
- 尽快检查结果。当您已经有一个无穷大的数值或NaN时,继续进行计算是没有意义的;或者,如果您的目标值根本没有被修改,则继续进行迭代。