2010-07-28 60 views
19

可能重複:
problem in comparing double values in C#爲什麼這個循環永不結束?

我在其他地方讀它,但真的忘了答案,所以我再問這裏。這個循環似乎從來不管最終你在任何語言代碼(我在C#中對其進行測試,C++,Java的...):

double d = 2.0; 
while(d != 0.0){ 
    d = d - 0.2; 
} 
+2

切勿使用具有浮動值的'=='。也許使用像'f> epsilon'這樣的東西。 – pascal 2010-07-28 08:36:03

+20

[這已經過了幾天了,我想我們已經過期了。](http://docs.sun.com/source/806-3568/ncg_goldberg.html) – GManNickG 2010-07-28 08:36:06

+0

在Java中,我認爲有「Strictfp」修飾符對於這種情況 – 2010-07-28 08:36:53

回答

32

浮點計算不是完全精確。你會得到一個表示錯誤,因爲0.2沒有精確的表示形式作爲二進制浮點數,所以這個值不會變成,正好等於零。嘗試添加調試語句,看問題:解決這個問題是使用類型decimal

double d = 2.0; 
while (d != 0.0) 
{ 
    Console.WriteLine(d); 
    d = d - 0.2; 
} 
 
2 
1,8 
1,6 
1,4 
1,2 
1 
0,8 
0,6 
0,4 
0,2 
2,77555756156289E-16 // Not exactly zero!! 
-0,2 
-0,4 

的一種方式。

+1

爲什麼會出現舍入錯誤?類型'double'存儲二進制數字。因爲0,2寫入的二進制是週期性分數,所以我們必須切斷某些東西來寫入N位的數字。 – adf88 2010-07-28 09:15:42

27

(一件事你沒有使用相同的變量貫穿始終,但我會認爲這是一個錯字:)

0.2是不是真的0.2。這是最接近double的值。當你從2.0中減去10次後,你將不會以正好爲 0.0。

在C#中,您可以更改使用decimal類型,而不是,這將工作:

// Works 
decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 0.2m; 
} 

這工作,因爲小數點型確實表示相同的0.2精確十進制值(在一定範圍內,它是一個128位類型)。所涉及的每個值都可以精確地表示出來,因此它很有用什麼不會工作將是這樣的:

decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 1m/3m; 
} 

這裏,「第三」是不能準確地表示,所以我們像以前一樣結束了同樣的問題。

一般來說,在浮點數之間執行精確的相等比較是一個壞主意 - 通常您會在一定的容差範圍內比較它們。

我在C#/。NET上下文中有關於floating binary pointfloating decimal point的文章,更詳細地解釋了這些內容。

1

f爲未初始化;)

如果你的意思是:

double f = 2.0; 

這可能是不準確的arthimetic的雙變量的影響。

3

您最好使用

while(f > 0.0) 

*編輯:請參閱下面帕斯卡爾的評論。但是,如果您確實需要運行一個循環,則需要確定的整數次數,而不是使用整數數據類型。

+0

我知道我應該使用它,但爲什麼f!= 0.0不起作用? – 2010-07-28 08:38:30

+1

如果最後一個'f'是2.0e-16,你可能運行一次太多...... – pascal 2010-07-28 08:38:42

1

這是因爲浮點的精度。使用while(d> 0.0),或者如果您必須,

while (Math.abs(d-0.0) > some_small_value){ 

} 
2

問題是浮點運算。如果沒有確切的數字二進制表示,那麼您只能存儲最接近的數字(就像您不能在十進制數中存儲數字1/3一樣 - 您只能存儲類似0.33333333的數據,長度爲'3')。這意味着浮點數的算術常常不完全準確。嘗試類似如下(JAVA):

public class Looping { 

    public static void main(String[] args) { 

     double d = 2.0; 
     while(d != 0.0 && d >= 0.0) { 
      System.out.println(d); 
      d = d - 0.2; 
     } 

    } 

} 

輸出應該是這樣的:

2.0 
1.8 
1.6 
1.4000000000000001 
1.2000000000000002 
1.0000000000000002 
0.8000000000000003 
0.6000000000000003 
0.4000000000000003 
0.2000000000000003 
2.7755575615628914E-16 

現在你應該能夠明白爲什麼條件d == 0從未發生過。 (最後一個數字有一個數字,是非常接近於0,但不太

對於浮點古怪的另一個例子,試試這個:

public class Squaring{ 

    public static void main(String[] args) { 

     double d = 0.1; 
     System.out.println(d*d); 

    } 

} 

因爲有恰好沒有二進制表示0.1,現蕾也不會產生你所期望的結果(0.01),但實際上像0.010000000000000002

+1

「絕對不準確」誇大了它,IMO。僅僅因爲在二進制浮點中不是每個*十進制值都不能精確表示,所以不會使得精確表示的0.25和0.25的加法精確地達到0.5。 – 2010-07-28 09:09:31

+0

編輯,感謝喬恩 - 這有點誇大了。 – Stephen 2010-07-28 09:29:52

10

我記得買辛克萊ZX-81,通過優良的基本編程手冊工作我的方式,幾乎返回當我遇到我的第一個浮點舍入錯誤時,我到了商店。

我從來沒有想到人們在27.99998年後仍然會遇到這些問題。

+2

ZX-Spectrum手冊附帶對此問題的詳細說明。我可以記得仔細想想(10歲左右或11歲左右),「哦,這很有道理」。這是一個非常好的手冊... – 2010-07-28 09:36:30

+7

+1爲27.99998年:-) – 2010-07-28 09:41:52

0

它不會停止,因爲0.2我不補 正是代表這樣的循環永遠不會執行0.0==0.0測試

+0

二進制補碼與浮點數的舍入誤差沒有任何關係。 – Egon 2012-11-17 21:11:28

0

正如其他人所說,這只是你做浮點時得到一個根本性的問題算術到任何基數。只是碰巧基2是計算機中最常見的一個(因爲它承認有效的硬件實現)。

如果可能的話,最好的解決方法是切換到使用循環數的某種商數表示,使得浮點值可以從中得出。好的,這聽起來很誇張!針對您的特殊情況下,我會寫爲:

int dTimes10 = 20; 
double d; 
while(dTimes10 != 0) { 
    dTimes10 -= 2; 
    d = dTimes10/10.0; 
} 

在這裏,我們真的有分數[20/10,18/10,16/10工作,...,2/10,0/10]在轉換爲浮點數之前,用固定的分母在分子中用整數(即容易得到正確)進行迭代。如果你可以重寫你的真實這樣的迭代,你將會獲得巨大的成功(而且它們確實沒有你之前做的那麼昂貴,這是獲得正確性的一個很好的權衡)。

如果你不能做到這一點,你需要在你的比較中使用相同的內部epsilon。大概,這將取代d != targetabs(d - target) < ε,其中ε(ε)選擇有時可能會很尷尬。基本上ε的正確值取決於一系列因素,但最好選擇0。001給出了步長值的比例(即它是步長幅度的百分之五十,因此這裏的任何內容都將是錯誤的而不是信息性的)。