Pio*_*icz 34 c floating-point floating-accuracy ieee-754
两个以下有什么区别?
float f1 = some_number;
float f2 = some_near_zero_number;
float result;
result = f1 / f2;
Run Code Online (Sandbox Code Playgroud)
和:
float f1 = some_number;
float f2 = some_near_zero_number;
float result;
result = (double)f1 / (double)f2;
Run Code Online (Sandbox Code Playgroud)
我对非常小的f2值特别感兴趣,这些值在浮点运算时可能产生+无穷大.是否有任何准确性?
使用这种演员表的一些实用指南也会很好.
Pat*_*han 31
我将假设IEEE 754二进制浮点运算,float
32位和double
64位.
通常,进行计算没有任何优势double
,在某些情况下,通过执行两个舍入步骤可能会使事情变得更糟.
从转换float
到double
是准确的.对于无限,NaN或零除数输入,它没有任何区别.给定有限数的结果,IEEE 754标准要求结果是实数除法的结果f1/f2
,四舍五入到在除法中使用的类型.
如果它是作为float
最接近float
确切结果的除法完成的.如果它是作为double
除法完成的,它将是最接近的double
,具有用于赋值的附加舍入步骤result
.
对于大多数输入,两者将给出相同的答案.任何在分区上没有发生的溢出或下溢,因为它完成double
将在转换时发生.
对于简单转换,如果答案非常接近两个float
值之间的中间值,则两个舍入步骤可能会选错float
.我原以为这也适用于除法结果.然而,Pascal Cuoq在对这个答案的评论中,提请注意一篇非常有趣的论文,Pierre Roux 的基本算术运算的无害双舍入,声称证明双舍入对于包括除法在内的多个运算(包括除法)是无害的.我在答案开始时做出的假设暗示了这一点.
如果单个浮点加法,减法,乘法或除法的结果立即存储到a float
,则不会使用double
中间值来提高精度.然而,在将操作链接在一起的情况下,通常使用更高精度的中间类型来提高准确性,前提是使用它们是一致的.在大约1986年的Turbo Pascal代码中:
Function TriangleArea(A: Single, B:Single, C:Single): Single
Begin
Var S: Extended; (* S stands for Semi-perimeter *)
S := (A+B+C) * 0.5;
TriangleArea := Sqrt((S-A)*(S-B)*(S-C)*S)
End;
Run Code Online (Sandbox Code Playgroud)
将扩展浮点运算的所有操作数扩展为Extended(80位浮点数),然后在存储到这些类型的变量时将它们转换回单精度或双精度.用于数值处理的非常好的语义.该区域的Turbo C表现相似,但无益于无法提供任何能够保持中间结果的数字类型; 当真正的问题是语言不能正确地支持它时,语言提供可能保持中间结果的变量类型的失败导致人们不公平地批评更高精度的中间结果类型的概念.
无论如何,如果要将上述方法编写成像C#这样的现代语言:
public static float triangleArea(float a, float b, float c)
{
double s = (a + b + c) * 0.5;
return (double)(Math.Sqrt((s - a) * (s - b) * (s - c) * s));
}
Run Code Online (Sandbox Code Playgroud)
如果编译器double
在执行计算之前恰好提升了加法的操作数,那么代码可以正常工作,但这可能会或可能不会.如果编译器执行计算float
,精度可能会很糟糕.例如,当使用上述公式来计算长边为16777215且短边为4的等腰三角形的面积时,急切促销将产生3.355443E + 7的正确结果,同时执行数学运算float
,具体取决于操作数的顺序,产量5.033165E + 7 [超过50%太大]或16777214.0 [超过50%太小].
请注意,即使像上面这样的代码在某些环境中可以正常工作,但在其他环境中产生完全虚假的结果,编译器通常也不会对这种情况发出任何警告.
尽管float
将要立即存储的各个操作float
可以按类型准确地完成float
,但是double
当操作组合时,急切地提升操作数通常会有很大帮助.在某些情况下,重新安排操作可以避免因促销失败而导致的问题(例如,上述公式使用五个加法,四个乘法和一个平方根;将公式重写为:
Math.Sqrt((a+b+c)*(b-a+c)*(a-b+c)*(a-c+b))*0.25
Run Code Online (Sandbox Code Playgroud)
将添加次数增加到8次,但即使以单精度执行也能正常工作.