在进行浮动分割时,在进行双击和后退时是否有任何准确度增益?

时间:2015-02-05 12:17:27

标签: c floating-point floating-accuracy ieee-754

两个人之间的区别是什么?

float f1 = some_number;
float f2 = some_near_zero_number;
float result;

result = f1 / f2;

float f1 = some_number;
float f2 = some_near_zero_number;
float result;

result = (double)f1 / (double)f2;

我对非常​​小的f2值感兴趣,这些值在浮点运算时可能会产生+无穷大。是否有任何准确性可以获得?

使用这种演员表的一些实用指南也很不错。

3 个答案:

答案 0 :(得分:31)

我将假设IEEE 754二进制浮点运算,float 32位且double 64位。

一般来说,在double中进行计算没有任何优势,在某些情况下,通过执行两个舍入步骤可能会使事情变得更糟。

floatdouble的转换是准确的。对于无限,NaN或零除数输入,它没有任何区别。给定有限数字的结果,IEEE 754标准要求结果是实数除f1/f2的结果,四舍五入到在除法中使用的类型。

如果以float分区完成,则该分区距离确切结果最近float。如果以double除法完成,则它将是距离double最近的result,并且还有一个额外的舍入步骤。

对于大多数输入,两者将给出相同的答案。因为在double中完成而在分区上未发生的任何上溢或下溢将在转换时发生。

对于简单转换,如果答案非常接近两个float值之间的中间值,则两个舍入步骤可能会选择错误的float。我原以为这也适用于除法结果。然而,Pascal Cuoq在对这个答案的评论中,引起了人们对皮埃尔·鲁克斯(Pierre Roux)的一篇非常有趣的论文Innocuous Double Rounding of Basic Arithmetic Operations的关注,声称证明双重舍入对包括分裂在内的几个操作是无害的,条件是我在答案开始时做出的假设。

答案 1 :(得分:6)

如果单个浮点加法,减法,乘法或除法的结果立即存储到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;

会将浮点运算的所有操作数扩展为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));
    }

如果编译器在执行计算之前恰好将double的加法操作数提升为float,那么代码将运行良好,但这可能会或可能不会。如果编译器执行计算为float,精度可能会很糟糕。例如,当使用上面的公式来计算长边为16777215且短边为4的等腰三角形的面积时,急切促销将产生3.355443E + 7的正确结果,同时将数学运算为float将取决于操作数的顺序,将产生5.033165E + 7 [超过50%太大]或16777214.0 [超过50%太小]。

请注意,尽管上述代码在某些环境中可以完美运行,但在其他环境中产生完全虚假的结果,但编译器通常不会对此情况发出任何警告。

虽然float上将立即存储到float的单个操作可以使用double类型与Math.Sqrt((a+b+c)*(b-a+c)*(a-b+c)*(a-c+b))*0.25 类型一样准确,但可以使用{{1}}类型,当操作结合起来时,急切地推广操作数通常会有很大帮助。在某些情况下,重新安排操作可以避免因促销失败而导致的问题(例如,上述公式使用五个加法,四个乘法和一个平方根;将公式重写为:

{{1}}

将添加次数增加到8次,但即使以单精度执行也能正常工作。

答案 2 :(得分:3)

“在进行浮动分割时,精度会增加到双倍和后退?” 结果取决于除了2个发布的方法之外的其他因素。


C允许评估float操作在不同级别发生,具体取决于FLT_EVAL_METHOD。 (见下表)如果当前设置为1或2,OP发布的两种方法将提供相同的答案。

根据其他代码和编译器优化级别,可以在OP的任何一种情况下的后续计算中以更高的精度使用商result

因此,float除法由于极float值而溢出或变为0.0(精度完全丧失的结果),并且如果针对后续计算进行了优化,实际上可能不会结束/当流量为double时。

要强制商在{* 1}}进行潜在优化的未来计算,代码通常会使用float

volatile

C没有指定数学运算的精度,但像IEEE 754这样的标准的常见应用提供了像binary32除法的单个运算,这将导致最接近的答案可表示。如果差异发生在volatile float result = f1 / f2; double等更广泛的格式,那么更宽的商转换回long double会经历另一个舍入步骤,在极少数情况下会产生与直接不同的答案float


  

float/float
  FLT_EVAL_METHOD不确定;
  -1仅根据类型的范围和精度评估所有操作和常量;
  0评估1float类型的操作和常量   double类型的范围和精度,评估double操作和常量到long double类型的范围和精度;
  long double评估所有操作和常量的范围和精度   2类型。

实用指南:
在需要时使用long doublefloat来节省空间。 (double通常比float更窄,很少相同。)如果精确度很重要,请使用double(或double)。

使用long doublefloat提高速度可能可能不会作为平台的本机操作可能都是{{1} }。它可能更快,更快或更慢 - 可以找到。 C的大部分最初设计为double,因为除了double到/ double转换之外,只执行了FP级别。后来C添加了double之类的功能,以便更快,更直接的float操作。因此编译器/平台越现代化,sinf()越有可能更快。再次:简介找出。