2013-05-18 149 views
6

Math.Round(8.075, 2, MidpointRounding.AwayFromZero)返回8.07,雖然它應該返回8.08。神祕地,7.075工作正常,但9.075也返回9.07Math.round bug - 該怎麼辦?

怎麼辦?有沒有人知道沒有這種錯誤的舍入方法?

+2

我很懷疑.NET都會有這樣的錯誤,如果是什麼結果,那麼我敢肯定,這是實現這種方式的一個原因。 –

+2

您可以使用小數類型,因爲它們更精確:'Math.Round(8.075m,2,MidpointRounding。AwayFromZero)' –

+0

我們需要Jon Skeet在這裏 – VladL

回答

4

我不是一個.NET專家,但這些數字不能準確地表示爲雙,所以如果你考慮到那些3個數字的真正價值四捨五入精確:

7.075 ==> 7.07500000000000017763568394002504646778106689453125 
8.075 ==> 8.074999999999999289457264239899814128875732421875 
9.075 ==> 9.074999999999999289457264239899814128875732421875 

更多關於浮點精度:What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

你是如何找到真正的價值的? –

+0

@AshBurlaczenko用java程序,但我想你可以在.NET中做同樣的事情。 – assylias

+0

@AshBurlaczenko:我在這裏有一個C#程序:http://blogs.msdn.com/b/ericlippert/archive/2011/02/17/looking-inside-a-double.aspx和最近的後續行動張貼在這裏:http://ericlippert.com/2013/05/13/spot-the-defect-rounding/ –

9

如果用10個指頭算,像人類這樣做,你不要有任何麻煩表示十進制值8.075正是:

8.075 = 8 x 10^1 + 0 x 10^0 + 7 x 10^-1 + 5 x 10^-2 

但電腦與2個掐算,他們需要表達該值2的冪:

8.075 = 1 x 2^3 + 0 x 2^2 + 0 x 2^1 + 0 x 2^0 + 0 x 2^-1 + 0 x 2^-2 + 0 x 2^-3 + 
      1 x 2^-4 + 0 x 2^-5 + 0 x 2^-6 + 1 x 2^-7 + 1 x 2^-8 + 0 x 2^-9 + 0 x 2^-10 + 
      1 x 2^-11 + ... 

我放棄了用手指抽筋鍵入條件,但問題是,無論第2多強你怎麼加,你就永遠不會得到準確8.075米。一個類似的問題是人類無法精確地寫出10/3的結果,它在分數中有無限數量的數字。用6個手指數時,只能正確地寫出該表達式的結果。

處理器當然沒有足夠的存儲來存儲無限數量的位來表示值。所以他們必須截斷數字序列,double類型的值可以存儲53位。

因此,當存儲在處理器中時,十進制值8.075會變爲四捨五入。轉換回十進制的53位序列的值爲〜8.074999999999999289。然後,如預期的那樣,通過您的代碼四捨五入到8.07。

如果您需要10個手指數學結果,則需要使用以10爲基數存儲數字的數據類型。這就是.NET中的System.Decimal類型。修復:

decimal result = Math.Round(8.075m, 2, MidpointRounding.AwayFromZero) 

請注意字母m在8.075m文字中的用法,該文字是片段中的一個十進制的文字。它選擇用10個手指計數的Math.Round()超載,之前您使用的是使用System.Double(2指版本)的超載。

請注意,使用System.Decimal計算存在明顯的缺點,它是。比使用System.Double計算速度慢得多,這是處理器直接支持的值類型。十進制數學是用軟件完成的,並不是硬件加速的。

+1

這就是爲什麼*真正*數學家[使用60指數](http://en.wikipedia.org/wiki /六十進制)。 – svick

-2

可能的解決方案可能是:

(double)Math.Round((decimal)8.075, 2, MidpointRounding.AwayFromZero);