如何使用 double 更安全和精确?

How to use double to be more secure and precise?

每种情况下的陈述在数学上都是等价的。我的问题是在编码时选择哪个更好。哪部分代码可能会导致某些范围的变量溢出,而另一部分代码不会溢出相同的范围。哪部分代码更精确,为什么?

double x, y, z;

//case 1
x = (x * y) * z;
x *= y * z;

//case 2
z = x + x*y;
z = x * ( 1.0 + y);

//case 3
y = x/5.0;
y = x*0.2;
// Case 1
x = (x * y) * z;
x *= y * z;

// Case 2
z = x + x*y;
z = x * ( 1.0 + y);

// Case 3
y = x/5.0;
y = x*0.2;

案例1:x *= y * z;类似于x = x * (y * z);所以这个案例强调了求值顺序。如果子产品超出计算范围并转换为 INF0.0 或次正规,则最终产品将受到显着影响,具体取决于顺序。 OTOH,中间数学可以在更广泛的 FP 类型下执行。搜索 FLT_EVAL_METHOD。在那种情况下,如果所有计算都按 long double.

进行,则顺序可能无关紧要

案例2:2种形式略有不同。第二个在数值上更稳定,因为 addition/subtraction 使用精确值:1, y 与第一个 x, x*y 相比,x*y 可能是一个四舍五入的答案。 Additional/subtraction 容易出现严重的精度损失 - 在这种情况下 y 接近 -1.0。作为案例 1,更广泛的中级数学有帮助,但第二种形式仍然更好。

C11 (C99?) 提供 fma(double x, double y, double z) 并且使用 fma(x, y, x) 将是另一个不错的选择。

The fma functions compute (x × y) + z, rounded as one ternary operation: they compute the value (as if) to infinite precision and round once to the result format, according to the current rounding mode. A range error may occur.

案例三:

这里的"trick"是double 0.2和数学上的0.2一样吗?通常不是——但它们很接近。然而,优化编译可以 1) 将它们视为相同或 2) 或与情况 1 一样,使用更广泛的数学。那么两行代码的结果是一样的

否则:根据舍入模式,这两种形式可能会在最小位 (ULP) 上有所不同。使用较弱的编译器,推荐 /5.0

除以 5.0 比乘以大约 0.2 更准确。但是无论以哪种方式编码,智能编译器都可以对两者进行广泛的乘法运算。