【问题标题】:Is there any accuracy gain when casting to double and back when doing float division?在进行浮点除法时转换为双精度并返回时是否有任何精度提升?
【发布时间】:2015-04-05 07:07:25
【问题描述】:

下面两个有什么区别?

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 值特别感兴趣,在浮点数上操作时可能会产生 +infinity。是否有任何准确性需要获得?

使用这种演员的一些实用指南也会很好。

【问题讨论】:

  • 如果您担心舍入错误,为什么要首先使用浮点数?
  • 因为我在 RAM 中保留了巨大的结构(几 GB 或更多),并且使用双精度不是存储的选项;不过,在进行计算时,来回转换是一种选择;
  • 值得注意的事实:x86 使用 80 bits 进行浮点除法,无论类型是 32 位还是 64 位。

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


【解决方案1】:

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

会将浮点运算的所有操作数扩展为扩展类型(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 执行计算,精度可能会很糟糕。例如,当使用上述公式计算长边为 16777215 且短边为 4 的等腰三角形的面积时,Eager Promotion 将产生正确的结果 3.355443E+7,同时按照 float 执行数学运算,根据操作数的顺序,产生 5.033165E+7 [超过 50% 太大] 或 16777214.0 [超过 50% 太小]。

请注意,即使上面的代码在某些环境下可以完美运行,但在其他环境下会产生完全虚假的结果,编译器通常不会对这种情况给出任何警告。

虽然 float 上的单个操作将立即存储到 float操作相结合。在某些情况下,重新排列操作可能会避免因失去提升而导致的问题(例如,上面的公式使用了五次加法、四次乘法和一个平方根;将公式重写为:

Math.Sqrt((a+b+c)*(b-a+c)*(a-b+c)*(a-c+b))*0.25

将加法的数量增加到 8 个,但即使它们以单精度执行也能正常工作。

【讨论】:

    【解决方案2】:

    我将假设 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 的关注,声称证明在假设所隐含的条件下,双舍入对于包括除法在内的多种运算是无害的我在这个答案的开头做了。

    【讨论】:

    • 请注意,/ 是在中间格式的有效数字至少是最终格式有效数字的两倍宽时不受双舍入影响的操作之一。当中间格式是 binary64 而最终格式是 binary32 时就是这种情况。 Figueroa 用正常的中间结果证明了这一点,而 Pierre Roux 似乎已决定对所有情况进行正式验证:hal.archives-ouvertes.fr/hal-01091186/document
    • @PascalCuoq 感谢您提供的信息,我已将其折叠到答案中。
    【解决方案3】:

    “在进行浮点除法时转换为 double 和 back 时的准确度提高?”
    结果取决于除了发布的 2 个方法之外的其他因素。


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

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

    因此,由于极端的float 值,float 除法溢出或变为 0.0(完全丧失精度的结果),如果针对后续计算进行优化,实际上可能不会溢出/不足商被结转为double

    为了迫使商成为float 以供将来在潜在优化中进行计算,代码通常使用volatile

    volatile float result = f1 / f2;
    

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


    FLT_EVAL_METHOD
    -1 不确定;
    0 仅根据类型的范围和精度评估所有运算和常量;
    1 评估 float 类型的运算和常量和double double 类型的范围和精度,将 long double 运算和常量评估为 long double 类型的范围和精度;
    2 将所有运算和常量评估为 long double 类型。

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

    使用floatdouble 来提高速度可能可能不作为平台的本机操作可能都是double。它可能更快,相同或更慢 - 找出来。大部分 C 语言最初是用double 设计的,因为除了double 到/从float 转换之外,只执行了级别FP。后来C增加了sinf()之类的功能,以方便更快、更直接的float操作。因此,编译器/平台越现代,float 的速度就越快。再次:配置文件找出来。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-02-07
      • 1970-01-01
      • 2015-06-12
      • 2019-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多