在进行浮点除法时加倍和返回时是否有任何精度增益?

Is there any accuracy gain when casting to double and back when doing float division?

下面两个有什么区别?

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。是否有任何准确性可以获得?

使用这种类型转换的一些实用指南也很好。

我将采用 IEEE 754 二进制浮点运算,float 32 位和 double 64 位。

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

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

如果它作为一个 float 的除法完成,则最接近 float 确切的结果。如果它作为 double 除法完成,它将是最接近的 double 并为分配给 result.

额外的舍入步骤

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

对于简单转换,如果答案非常接近两个 float 值的一半,则两个舍入步骤可能会选择错误的 float。我原以为这也适用于除法结果。但是,Pascal Cuoq 在对此答案的评论中提请注意一篇非常有趣的论文 Innocuous Double Rounding of Basic Arithmetic Pierre Roux 的 Operations,声称在我在此答案开头所做的假设所暗示的条件下,双舍入对包括除法在内的多种操作无害。

"Accuracy gain when casting to double and back when doing float division?"
结果取决于除了仅发布的 2 种方法之外的其他因素。


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

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

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

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

volatile float result = f1 / f2;

C 没有指定数学运算的精度,但 IEEE 754 provide the a single operation like binary32 除法等标准的常见应用将导致最接近的答案表示 table。如果除法以 doublelong double 之类的更宽格式出现,则更宽的商转换回 float 会经历另一个舍入步骤,在极少数情况下会导致与直接答案不同的答案float/float.


FLT_EVAL_METHOD
-1 indeterminable;
0 evaluate all operations and constants just to the range and precision of the type;
1 evaluate operations and constants of type float and double to the range and precision of the double type, evaluate long double operations and constants to the range and precision of the long double type;
2 evaluate all operations and constants to the range and precision of the long double type.

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

使用 floatdouble 来提高速度 可能 可能不会 作为平台的本机工作操作可能都是double。它可能更快,相同或更慢 - 找出来的配置文件。大部分 C 最初是用 double 设计的,因为除了 double to/from float 转换之外,只执行了级别 FP。后来 C 添加了 sinf() 等函数,以促进更快、更直接的 float 操作。所以 compiler/platform 越现代,float 越有可能会更快。再次:简介一探究竟。

如果将单个浮点加、减、乘或除的结果立即存储到 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 执行计算,精度可能会很糟糕。例如,当使用上述公式计算长边为 16777215、短边为 4 的等腰三角形的面积时,急切提升将产生 3.355443E+7 的正确结果,同时执行 float 的数学运算根据操作数的顺序,将产生 5.033165E+7 [超过 50% 太大] 或 16777214.0 [超过 50% 太小]。

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

尽管 float 上的个别操作将立即存储到 float 中,但使用类型 float 可以像使用类型 [=14= 一样准确地完成它们],当操作组合时,急切地提升操作数通常会有很大帮助。在某些情况下,重新排列操作可能会避免因提升损失而引起的问题(例如上面的公式使用五次加法,四次乘法和一个平方根;将公式重写为:

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

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