2013-08-01 53 views
34

我注意到一個奇怪的行爲,當乘以C#中的十進制值。考慮下面的乘法運算:C#小數乘法奇怪的行爲

1.1111111111111111111111111111m * 1m = 1.1111111111111111111111111111 // OK 
1.1111111111111111111111111111m * 2m = 2.2222222222222222222222222222 // OK 
1.1111111111111111111111111111m * 3m = 3.3333333333333333333333333333 // OK 
1.1111111111111111111111111111m * 4m = 4.4444444444444444444444444444 // OK 
1.1111111111111111111111111111m * 5m = 5.5555555555555555555555555555 // OK 
1.1111111111111111111111111111m * 6m = 6.6666666666666666666666666666 // OK 
1.1111111111111111111111111111m * 7m = 7.7777777777777777777777777777 // OK 
1.1111111111111111111111111111m * 8m = 8.888888888888888888888888889 // Why not 8.8888888888888888888888888888 ? 
1.1111111111111111111111111111m * 9m = 10.000000000000000000000000000 // Why not 9.9999999999999999999999999999 ? 

我無法理解的是以上兩種情況。這怎麼可能?

+11

歡迎來到精確錯誤的美妙世界。 – christopher

+0

這不是精確錯誤,它只是四捨五入。 –

+0

這是你的一個數學難題:'10/9 = 1.111 ...'; '1.111 ... * 9 = 9.999 ...≠10'。 ;) – stakx

回答

73

decimal存儲28或29位有效數字(96位)。基本上尾數在 -/+ 79,228,162,514,264,337,593,543,950,335的範圍內。

這意味着高達約7.9 ....你可以準確地得到29位有效數字 - 但在上面你不能。這就是爲什麼8和9都出錯了,而不是早期的值。你應該只有依靠一般28位有效數字,以避免這種奇怪的情況。

一旦你降低你的原始投入28個顯著的數字,你會得到你所期望的輸出:

using System; 

class Test 
{ 
    static void Main() 
    { 
     var input = 1.111111111111111111111111111m; 
     for (int i = 1; i < 10; i++) 
     { 
      decimal output = input * (decimal) i; 
      Console.WriteLine(output); 
     } 
    } 
} 
+0

我不確定你的意思是「依靠」28位數字。你的意思是明確地將諸如分裂結果之類的東西四捨五入嗎? 「Decimal」使用基數爲10的指數而不是基數爲2的事實意味着「Decimal」的名義數值將與其簡潔的字符串表示相匹配,但是它是浮點類型的事實表明等式用'Decimal'測試可能會遇到與任何其他浮點類型相同的問題。 – supercat

+0

@supercat:我的意思是,如果你有29位數字需要表示,你可能無法完全做到,但你可以完全代表28位數字。我認爲依賴於'decimal'的平等比'float'/'double'更合理 - 一方面,您不必擔心某些操作可能更準確地執行,這取決於價值在寄存器中或不在。它仍然必須小心謹慎地做,但在很多情況下,我認爲它會好起來的。 –

2

數學家有理數和超實數區分。有理數的算術運算是明確的和精確的。對實數進行算術運算(使用加法,減法,乘法和除法運算符)只是「精確」的,無論是無理數還是非理性形式(符號)或可能在某些表達式中轉換爲有理數。例如,兩個平方根沒有小數(或任何其他有理基數)表示。但是,2的平方根乘以2的平方根顯然是有理的-2。

計算機及其上運行的語言通常只實現有理數 - 隱藏在諸如int,long int,float,double precision,real(FORTRAN)之類的名稱後面或其他建議實數的名稱之後。但是包含的有理數是有限的,不像有限數的範圍是無窮的。

微不足道的示例 - 未在計算機上找到。 1/2 * 1/2 = 1/4如果您有一個有理數類,並且分子和分母的大小不超過整數算術的限制,那麼這很有效。所以(1,2)*(1,2) - >(1,4)

但是,如果可用的有理數是十進制並限於小數點後的一位數 - 不切實際 - 但代表選擇時選擇一個用於近似有理數(float/real等)數值的實現,那麼1/2將完全轉換爲0.5,那麼0.5 + 0.5將等於1.0,但0.5 * 0.5將不得不爲0.2或0.3!