天天看點

對非正确使用浮點型資料而導緻項目BUG的問題探讨

乘法配置設定律

在上國小的時候就已經學習過乘法配置設定律,乘法配置設定律的具體内容是:兩個數的和與一個數相乘,可以先把他們分别與這個數相乘,再相加,得數不變。乘法配置設定律的定義還可以用表達式“(a+b)×c = a×c+b×c”的形式給出。乘法配置設定律的反用“a×c+b×c = (a+b)×c”同樣成立。例如“10.2×(3+7) = 10.2×3+10.2×7 = 102”(反用形式為“10.2×3+10.2×7 = 10.2×(3+7) = 102”)。 

計算機世界中的乘法配置設定律

為了一窺計算機世界中的乘法配置設定律,本文給出以下執行個體進行探究。

本例中先将整數24(紀念兒時玩過的名為“24點”的遊戲)分割成四個整數的和,此部分操作在GetNumberList()方法中實作,分割後的四個整數儲存進結構體變量中,整數24的所有分割結果則儲存進List<Number>類型的集合numList中。接着周遊集合numList,每次周遊中先取出24的四個分割數分别與5相乘再相加(相當于乘法配置設定律中的a×c+b×c部分)得到結果result1,再将計算的結果result1與24 * 5(相當于乘法配置設定律中的(a+b)×c部分)的結果result進行比較,相等則輸出true,否則輸出false。

運作程式,得到下圖所示結果。

對非正确使用浮點型資料而導緻項目BUG的問題探讨

大家從圖中能看出什麼呢?

【DD(不是“小弟弟”的縮寫,至于是什麼,你猜):我從圖中可以看出,顯示結果不全。

   作者:呵呵,DD真是觀察細緻入微。結果是不全,但是本人通過拖動滾動條已經确認所有傳回結果都是true】

從結果了解到對于整型資料來說,乘法配置設定律完全适用。

那麼将例子中的整型資料換成Double型資料,結果又會怎樣呢?

修改執行個體中的部分代碼,其他代碼保持不變,下面僅給出修改部分的代碼。

執行修改後的代碼,得到下圖所示的結果。當然,這裡給出的結果也是不全的,不過對說明問題沒有任何影響。

對非正确使用浮點型資料而導緻項目BUG的問題探讨

從圖中可以看出,将整型資料換成Double型資料後,傳回的結果中出現了相當數量的false,也就是說result與result1不再是絕對地相等了。我們可以通過Debug程式看看其中到底發生了什麼。 下面以圖中顯示的傳回結果為false的第一條資料為例。看看此時的result1的值是多少,為4409.0000000000009,而result的值為4409.0,很顯然将兩者進行相等比較,自然會傳回false,雖然兩者僅相差0.0000000000009,不相等就是不相等。

那麼,0.0000000000009的差距是怎樣産生的呢。

我們知道計算機在計算表達式“result1 = n.Num1 * 183.70833333333334  + n.Num2 * 183.70833333333334 + n.Num3 * 183.70833333333334 + n.Num4 * 183.70833333333334;”的值時,其實會将運算分解成多步,用代碼來表示其運算過程的話,應該與以下代碼所示的運算過程類似。

而我們知道,浮點運算是不準确的,因為:計算機在處理浮點數的時候,會先把浮點數(float , double)轉換成整數再轉換成二進制,然後進行操作,如果有取餘,會有不同的取餘方式。 再加上運算完成後,将二進制轉換成上層的浮點數時,又會有一些取舍。這樣一來,就會使最終的計算結果存在一定的誤差。

再回到我們的問題,計算“double result = 24 * 183.70833333333334”會進行一次浮點運算,而計算“result1 = n.Num1 * 183.70833333333334+ n.Num2 * 183.70833333333334+ n.Num3 * 183.70833333333334+ n.Num4 * 183.70833333333334”時至少會進行5次浮點運算或者更多(這裡有點拿不準),每一次浮點運算都有可能伴随着誤差的發生,是以導緻最終看到的結果會随機性地産生一些偏差。是以,在計算機的世界中,乘法配置設定律将不再适用,當然如果你對這一點點的損失無動于衷的話,你也可以認為在計算機世界中乘法配置設定律相對地适應于浮點運算。

不過,這個問題隐藏的也太深了點吧。

非正确使用浮點型資料導緻項目BUG了

在開發過程中要是沒有考慮到前文所述的問題,往往會導緻一些奇葩的BUG。

曾經在項目代碼中就看到了與執行個體代碼相差無幾的一段代碼,代碼中同樣使用if (result1 == result)來進行條件判斷,滿足此條件後再進行其他一些操作,項目上線後很長一段時間都相安無事,突然有一天這段代碼産生BUG了,至于BUG原因,就算我不說大家也清楚。

後來花了很大力氣才搞清楚了問題的所在,問題弄清楚後,也才有了本文的産生。

是以本文作者想告訴大家:不管怎麼說,開發中,在使用浮點型資料時,我們必須清楚浮點運算的特點,以免産生本文所述的類似的問題。

繼續閱讀