如果始终使用一种 IEEE-754 二进制浮点格式(例如,基本的 32 位二进制,C++ 常用的格式float),所有 C++ 运算符都以直接和简单的方式映射到 IEEE-754 运算,则lerp_symmetric(alpha, x0, x1)(以下简称A) 等于lerp_symmetric(1-alpha, x1, x0)( B)
证明:
- 如果
alpha我们假设在 [0, 1] 中的 大于或等于 ½,则1-alpha根据 Sterbenz 引理是精确的。(“精确”是指计算的浮点结果等于数学结果;没有舍入误差。)然后,在计算A时,w0因为它是1-alpha,所以w1是精确的,因为它的数学结果是alpha,所以它是精确的有代表性的。并且,在计算中B,w0因为它的数学结果是精确的alpha,并且w1因为它再次是精确的,所以它是精确的1-alpha。
- 如果
alpha小于 ½,则1-alpha可能有一些舍入误差。让结果是beta。那么,在 中A,w0是beta。现在 ½ ≤ beta,所以 Sterbenz 引理适用于 的评估w1 = 1.0f - w0,所以w1是精确的(并且等于 的数学结果1-beta)。并且,在 中B,w0是精确的,同样由 Sterbenz 引理,并且等于w1,A并且w1(of B) 是精确的,因为它的数学结果是beta,它是完全可表示的。
现在我们可以看到w0inA等于w1inB和w1inA等于w0in B。在上述任何一种情况下都设beta为,因此分别返回和。IEEE-754 加法是可交换的(NaN 有效载荷除外),因此返回相同的结果。1-alphaAB(1-beta)*x0 + beta*x1beta*x1 + (1-beta)*x0AB
回答问题:
我会说这是一个合理的方法。我不会断言没有进一步思考就无法做出改进。
不,你不能相信你的编译器:
- C++ 允许实现在评估浮点运算时使用超额精度。因此,即使所有操作数都是 ,
w0*x0 + w1*x1也可以使用double,或其他精度进行评估。long doublefloat
- C++ 允许收缩,除非禁用,因此
w0*x0 + w1*x1可以评估为fmaf(w0, x0, w1*x1),因此对其中一个乘法使用精确算术,但对另一个不使用。
您可以使用以下方法部分解决此问题:
float w0 = 1.0f - alpha;
float w1 = 1.0f - w0;
float t0 = w0*x0;
float t1 = w1*x1;
return t0+t1;
C++ 标准要求在赋值和强制转换中丢弃多余的精度。这扩展到函数返回。(我从内存中报告了这个和其他 C++ 规范;应该检查标准。)因此,float即使最初使用了额外的精度,上述每一项都会将其结果四舍五入。这将防止收缩。
(还应该能够通过包含<cmath>和插入预处理器指令来禁用收缩#pragma STDC FP_CONTRACT off。一些编译器可能不支持。)
上述解决方法的一个问题是,值首先四舍五入到评估精度,然后四舍五入到float. 有一些数学值,对于这样的值x ,首先将 x 舍入到(或double另一个精度)然后产生与将x直接float舍入到 不同的结果。Samuel A. Figueroa del Cid的论文A Rigrous Framework for Fully Supporting the IEEE Standard for Floating-Point Arithmetic in High-Level Programming Languages确定了评估 IEEE-754 基本 64 位浮点中的单次乘法或加法运算(通常用于floatdouble) 然后舍入到 32 位格式永远不会出现双舍入错误(因为这些操作,给定的输入是 32 位格式的元素,永远不会产生上述麻烦的x值之一)。1
如果我对从内存报告的 C++ 规范是正确的,那么只要 C++ 实现使用标称格式或足够宽的格式来评估浮点表达式以满足 Figueroa del Cid 给出的要求,那么上述解决方法应该是完整的.
脚注
1根据 Figueroa del Cid,如果x和y具有p位有效数,并且x+yorx*y被精确计算然后四舍五入到q位,则第二次四舍五入到p位将得到相同的答案,就好像结果直接四舍五入到p位如果p ≤ ( q - 1 )/2。这对于 IEEE-754 基本 32 位二进制浮点 ( p = 24) 和 64 位 ( q = 53) 是满足的。这些格式通常用于floatand double,上面的解决方法在使用它们的 C++ 实现中应该足够了。如果评估了 C++ 实现float使用不满足 Figueroa del Cid 给出的条件的精度,则可能会出现双舍入误差。