1. 程式人生 > >從一道招聘考題談起

從一道招聘考題談起

潤乾研發部在招聘時有一個筆試題:

1/2,1/5,1/20,1/64,1/125 都可以寫成有限小數,而 1/3,1/7,1/15,1/24 則必須寫成無限迴圈小數。請指出能寫成有限小數的分數具有什麼樣的特徵?在什麼情況下 1/5 也會被寫成無限迴圈小數?

坦白地說,這個題的通過率並不高,不到一半吧。

仔細分析題目中的分母,我們會發現,這些能寫成有限小數的分母,分解質因數之後就都只有 2 和 5 這兩種質因子。而那些不能寫成有限小數的分母分解因數後則含有不是 2 和 5 的質因子。也就是說,只有當分母可以寫成 2^n*5^m 這種形式時才可能寫成有限小數。

這是為什麼呢?

其實很簡單,因為我們把這些分數寫成小數時使用的是 10 進位制。而 10=2*5,對於任何一個形如 2^n*5^m 的分母,設 k=max(n,m),則把這個數乘以 10^k,即 2^k*5^k,就一定會變成一個整數了,也就是說,這個數的小數部分最多隻有 k 位,這當然是有限的。而如果分母中含有其它質因子,則不可能有個 k 使得讓這個數乘以 10^k 後變成整數,也就只能是無限小數了。

那麼,什麼時候 1/5 會寫成無限小數呢?

如果我們採用的數制不是 5 的倍數,就會發現這種情況了,比如計算機普遍採用的 2 進位制,這時候 1/5 會寫成一個無限迴圈小數。

但是,我們的機器都是有限位數的,不可能真地表示一個無限位的數,只能捨棄後面的位。也就是說,1/5 在計算機中是不能被精確表示的!

這個現象會影響到我們的程式設計。

比如我們寫一段這樣的程式碼:

double x = 0;

for ( int i=0; i<=1000; i++ )

x+=0.001;

我們現在想當然地認為 x 會等於 1,然而並不是!在我的機器上的 Java 環境中跑出來 x=1.0000000000000007,一個奇怪的結果。

為什麼要強調的是我的機器上的 Java 環境呢?因為浮點數的表示和 CPU 以及編譯器都有關係,換一臺機器或編譯器就可能會跑出不同的結果。

而且,結果也不總是變大。如果我們改成反覆加 1 萬次(即用 i<=10000),得到的 x=9.999999999999897,沒啥規律可言。

計算結果和預期值的誤差其實非常小,會有什麼後果嗎?

如果是用於後續再計算(比如再加減乘除等),這個誤差確實不重要,可以不去理它。但有時候我們可能會把它用於比較,再根據比較結果做下面的動作。比如我們預期這個 x 應當等於 1,如果後續是這樣的程式碼:

if ( x==1.0 ) {…} else {…}

那就會執行出錯誤的結果了,這個 bug 還很難被發現,程式碼邏輯上看完全沒有問題。而且由於前面所說的該現象出現的隨機性,也不是對任何數都一定會產生這個結果,很可能在測試時沒碰到而被放過了。

那麼,怎麼避免這個錯誤呢?

在涉及浮點數相等比較時,一般不要直接使用精確地相等去判斷,而要看差的絕對值是否小於某個很小的數。程式碼寫成:

if ( abs(x-1.0) < 1E-10) ….

就不會錯了。

如果目標比較值是整數,那還可以將計算結果轉換成整數,整數在 2 進位制下都可以精確表示,可以放心地用 == 去判斷,但注意要做四捨五入,即

if ( int(x+0.5) == 1 ) ….

如果直接用 int(x) 取整,在計算結果因舍位誤差小於預期結果時,也會出錯。

比較值不是整數,但能保證一定位數的精度,可以先用乘法再轉換成整數:

if ( int(x*1000+0.5) == 1000 ) …

還有的辦法就是避免使用浮點數。

我們知道,現代資料庫都提供有 decimal 資料型別,其實就是這麼個思路。decimal 可以稱為定點數,其小數部分也是按位數儲存的,計算時能夠精確表示,不會有上述的誤差。但是,decimal 不是現代 CPU 直接支援的資料型別,需要資料庫軟體來自行實現其計算邏輯,效能就會差出很多。所以,在不需要這種精度時(比如只是計算總數或平均值等),我們還是把它轉換成浮點數來計算更好一點。集算器在從資料庫取數時提供了 @d 選項就是為了自動把 decimal 轉成浮點數獲得高效能,但需要冒不精確的風險,所以做成選項由程式設計師自行根據場景決定。

實際業務中,需要精確比較的浮點數常常是金額。大多數國家的貨幣都是兩位小數的,這樣我們可以將數值先乘以 100 轉換成整數再儲存,而整數的運算和比較都是精確的,不會出現這種問題,但是在顯示時需要再轉換回來變成使用者習慣的兩位小數寫法。CPU 計算和處理整數的效能也非常高,64 位的 CPU 能夠表示的整數範圍在±2^63,即使除以 100 也還有 16 位整數部分,大約是 1 千萬億,這對於相當多的場景都夠用了。這樣就即有精確度又有高效能。

作者:279400248 連結:http://c.raqsoft.com.cn/article/1535513799636 來源:乾學院 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。