[英]Floating-point equality unexpectedly working
我們經常被告知,不應該比較浮點數是否完全相等。 然而,下面的 function 在傳遞任何正數時返回黃金比例,實際上確實比較雙精度數是否相等,令我驚訝的是它似乎總是有效:
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!
}
我認為它可能適用於某些輸入 arguments 但不適用於其他輸入。 但即使我使用 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 也返回最接近根的浮點數,我們將得到完全相等。
...令我驚訝的是它似乎總是有效:
不總是。
當我嘗試f(-1/1.6180339887498949)
時, x
和y
值在最后幾位不同的兩個浮點值之間振盪@Steve Summit 。 因此一個無限循環。
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
再次成為 x 並因此滿足停止條件。
更好的例程可以證明,如果x
相當接近, while
循環最終將接近所需的答案,但即便如此,如上所示的振盪也是可能的。 因此需要使用迭代限制或足夠接近的測試。 通常 2 個振盪值,當接近時,它們被按摩(例如平均)以形成最佳答案。 如果不關閉,則循環根本無法找到穩定的答案。
誰能告訴我為什么我從來沒有遇到過兩個浮點數相差很小的情況?
測試不充分。
故事的寓意:
不要只依賴浮點數相等,select 情況除外。
f()
不是 select 案例,應該有額外的停止代碼。
參考:具有數學屬性的兩個x
: x = 1 + 1/x
:
x1 = 1.6180339887498948482045868343656...
x2 = -0.61803398874989484820458683436564...
注意x1*x2 == -1
。 x1
是黃金比率 φ 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.