提问人:k314159 提问时间:1/12/2023 更新时间:1/13/2023 访问量:87
浮点相等性意外工作
Floating-point equality unexpectedly working
问:
我们经常被教导,浮点数不应该为了完全相等而进行比较。然而,以下函数在传递任何正数时返回黄金比例,实际上确实比较了双倍的相等性,令我惊讶的是,它似乎总是有效:
public static double f(double x) {
double y;
while ((y = 1 + 1 / x) != x)
x = (x + y) / 2;
return x;
}
@Test
void test() {
assertEquals((1 + sqrt(5)) / 2, f(1.0)); // Passes!
}
我认为也许它适用于某些输入参数,但不适用于其他参数。但即使我使用 JQwik 的属性测试,它仍然有效!
@Property
void test2(@ForAll @Positive double x) {
assertEquals((1 + sqrt(5)) / 2, f(x)); // Always passes!
}
谁能告诉我为什么我从来没有遇到过两个浮点数相差很小的情况?
答:
你只是运气好,一般来说,你没有得到完全的平等。例如,尝试以下操作:
public static void main(String[] args) {
var s = 0.0;
for (int i = 0; i < 10; i++) {
s += 0.1;
}
System.out.println(s == 1.0);
}
在你的具体例子中,必须进行仔细的分析,以证明你的迭代总是收敛到最接近 phi 的浮点数。如果 sqrt 还返回最接近精确根的浮点数,我们将得到完全相等。
评论
...令我惊讶的是,它似乎总是有效:
并非总是如此。
当我尝试时,和值在两个浮点值之间振荡,这两个浮点值在Summit的最后几位@Steve有所不同。因此是一个无限循环。f(-1/1.6180339887498949)
x
y
x:-0.61803398874989490 y:-0.61803398874989468 // Decimal notation
x:-0x1.3c6ef372fe950p-1 y:-0x1.3c6ef372fe94ep-1 // Hex notation
x:-0.61803398874989479 y:-0.6180339887498949
x:-0x1.3c6ef372fe94fp-1 y:-0x1.3c6ef372fe950p-1
x:-0.61803398874989490 y:-0.61803398874989468
x:-0x1.3c6ef372fe950p-1 y:-0x1.3c6ef372fe94ep-1
f(some_starting_x)
通常收敛以呈现一个 ,使得再次满足停止条件。x
1 + 1 / x
x
更好的例程可以证明,如果相当接近,循环最终会接近所需的答案,但即便如此,如上所示的振荡也是可能的。因此,需要使用迭代限制或足够接近的测试。通常,当 2 个振荡值接近时,它们会被按摩(例如平均)以形成最佳答案。如果不关闭,循环根本无法找到稳定的答案。x
while
谁能告诉我为什么我从来没有遇到过两个浮点数相差很小的情况?
测试不充分。
故事的士气:
不要只依赖浮点相等,除非在特定情况下。
不是选择案例,应该得到额外的停止代码。f()
Ref: 两个具有数学属性: :x
x = 1 + 1/x
x1 = 1.6180339887498948482045868343656...
x2 = -0.61803398874989484820458683436564...
注意。 是Golden_ratio φ。x1*x2 == -1
x1
上一个:浮点数学坏了吗?
评论
double
Math.sqrt
double
double