将一对几乎为一的值相乘能否得出 1.0 的结果?

Can multiplying a pair of almost-one values ever yield a result of 1.0?

我有两个浮点值,ab。我可以保证它们是域 (0, 1) 中的值。是否存在 a * b 可以等于 1 的情况?我打算计算 1/(1 - a * b),并希望避免被零除。

我的直觉是不能,因为结果应该等于或小于 ab。但是直觉并不能很好地代替理解正确的行为。

我不知道指定舍入模式,所以如果有舍入模式我可能会遇到麻烦,我想知道它。

编辑:我没有指定编译器是否符合 IEEE,因为我不能保证 compiler/CPU 运行 我的软件确实符合 IEEE。

有一个数学证明它永远不会> = 1。我手头没有....如果您有兴趣研究证明,您可能想在数学堆栈溢出网站上询问。但你的直觉是正确的。它永远不会 >= 1.

现在,我们必须小心,因为浮点运算只是数学的近似并且有局限性。我不是这些限制方面的专家,但浮点标准经过精心设计并提供了一定的保证。我很确定其中之一包括(或暗示)x * y where x < 1 and y < 1 is guaranteed to be < 1.

你可以检查一下,即使使用小于 1 的最高浮点数或双精度数,并与自身相乘,结果也将小于 1。任何小于它的数字相乘都必须得到更小的结果。

这是我 运行 的代码,结果在评论中:

float a = nextafterf(1, 0); // 0.999999940
double b = nextafter(1, 0); // 0.99999999999999989
float c = a * a; // 0.999999881
double d = b * b; // 0.99999999999999978

I have two floating point values, a and b

因为这表示我们有“值”,而不是“变量”,它承认 1 - a*b 可能计算为 1 的可能性。在编写软件时,人们有时会使用名称作为更复杂表达式的占位符。例如,一个人可能有一个表达式 asin(x)/x 和一个表达式 b1-y*y 然后询问计算 1 - a*b 当代码实际上是1 - (sin(x)/x)*(1-y*y)。这将是一个问题,因为 C++ 允许在评估浮点表达式时使用额外的精度。

最常见的情况是编译器在计算包含 double 个操作数的表达式时使用 long double 算术,或者在计算格式为 [=] 的表达式时使用融合乘加指令21=].

假设表达式 ab 是超精度计算的,并且在该超精度中是小于 1 的正值。例如,为了说明,假设 double 是用四位十进制数字实现的,但 ab 是用 long double 和六位十进制数字计算的。 ab 都可以是 .999999。那么a*b四舍五入前为.999998000001,四舍五入后为.999998。现在假设在计算的这一点上,编译器从 long double 转换为 double,这可能是因为它决定暂时将这个中间值存储在堆栈中,同时根据附近的表达式计算其他一些东西。将其转换为四位数 double 会产生 1.000,因为这是最接近 .999998 的四位小数。当编译器稍后从堆栈加载它并继续计算时,我们有 1 - 1.000,结果为零。

另一方面,如果 ab 是变量,我希望你的表达式是安全的。当一个值被赋值给一个变量或者用强制转换操作转换时,C++标准要求它被转换为标称类型;结果必须是标称类型的值,没有任何“额外精度”。然后,给定 0 < a < 1 和 0 < b < 1,数学值(没有浮点舍入)ab 小于 a 且小于 b。然后使用任何 IEEE-754 舍入方法将 ab 舍入到标称类型不能产生大于 ab 的值,因此它不能产生 1。(这里唯一的要求是舍入方法永远不会跳过值——它可能会被限制在特定方向上舍入,向上或向下或向零或其他方向舍入,但它永远不会超过该方向上的可表示值以获得值离未舍入的结果更远。因为我们知道 abab 限制,舍入不能产生任何大于 [= 中较小者的结果10=] 和 b.)

形式上,C++标准对浮点结果的精度没有任何要求。因此,C++ 实现可以使用 bonkers 舍入模式,为 .9*.9 生成 3.14。除了将次正规刷新为零的实现之外,我不知道有任何不符合上述要求的 C++ 实现。当 ab 接近 1 时,将次正规刷新为零不会影响 1 - a*b 中的计算。值,.9999 可以表示,而 .0001 不能表示,因为它所需的指数超出范围。然后 1-.9999*.9999,在正常的四位数算术中会产生 .0002,由于下溢而产生 0。否这种格式在普通硬件中。)

因此,如果 ab 是变量,则 0 < a < 1 且 0 < b < 1,并且您的 C++ 实现是合理的(可能使用额外的精度,可能刷新次正规,不使用反常的浮点格式或舍入),然后 1 - a*b 不计算为零。