什麼?計算機也會算錯數?
人們通常不會懷疑計算機在數學計算上的結果,畢竟,如果連數都算不對,那還叫什麼「計算機」啦?其實,有個簡單的題目就能讓計算機算錯,不信我們試試看。
如果你用Chrome瀏覽器的話,按下F12(在Mac上要按Command+Option+I)可以打開一個「開發者工具」窗口,然後點上面的「Console」標籤,你會看到一個控制台窗口,在裡面輸入「0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1」(一共10個0.1),按下回車,看看結果是什麼?
矮馬,嚇死本寶寶了,明眼人一看就知道算錯了是吧?10個0.1加起來應該等於1啊!不過如果你試一下「0.1*10」,咦,這次結果又對了!這什麼鬼?好吧,放心,你的電腦沒壞,我們來看看這到底是怎麼回事。
稍早接觸計算機的那一代人幾乎都知道計算機的內部運算都是以二進位來進行的,然而現在的用戶恐怕早就不關心這件事了。自從馮·諾依曼提出現代計算機架構以來,幾十年了計算機的原理都沒有什麼根本性的變化。我們人類在日常生活中通常使用十進位,有一種說法認為這是因為人有10個手指,但在電路設計上要表示10個狀態可就相當困難了,不過很多元件都可以很容易地表示兩個狀態,比如開和關,電平高和低,因此採用二進位對於計算機來說是一個最方便的設計。而正是因為人類和計算機使用的進位不同,因此在進行計算時,就必然會涉及到進位的轉換,也就是說,我們在屏幕上輸入的是十進位的數字,然後計算機要將它轉換成二進位進行計算,然後再把計算的結果轉換成十進位顯示出來。之所以上面那個題目計算機會算錯,本質上說就是二進位惹的禍。
要搞清楚這個問題,我們先來理解一下進位的概念。在十進位中,一位數字我們可以使用0到9,比9多的時候就會變成10,這就是一個兩位數了,也就是進位了,二進位也是一樣,只不過一位數字只能使用0和1,再多就要進位了。那麼進位的本質又是什麼呢?我們隨便拿一個十進位的數字來看一看:
看懂了沒?一個十進位數實際上就是其中每一位數依次乘以10的0、1、2…次冪(權重),然後再把結果加起來,那麼以此類推,二進位裡面就是把上面的10換成2唄?我們來看一個:
於是二進位數1001也就是十進位數的9。到這裡似乎還沒什麼問題,因為我們只討論了整數呢,每一個十進位整數都可以轉換成一個二進位整數,反過來,每一個二進位整數也都可以轉換成一個十進位整數。不過,如果把小數也加進來呢?先看一個十進位的小數:
看懂了沒?其實就是把10上面的指數變成了負數而已,不難吧。那麼以此類推,二進位的小數也就是把10換成2唄,我們來看一個:
上面我們理解了進位的一些本質特性,算不過來也沒關係,我們暫且先不管它,不過,這跟我們遇到的問題到底有什麼關係?別急,我們再看一下當引入小數之後,進位之間的轉換到底出了什麼bug。我們知道實數的數軸是連續的,每兩個數字之間的部分是可以被無限分割的,舉個例子,0和1之間的這部分,如果用十進位一位小數來分割的話,可以分成10份,也就是0.1、0.2、0.3……0.9、1,如果用二進位一位小數來分割的話,則只能分成兩份,也就是0.1(十進位的0.5)、1。你發現了什麼問題?無論小數點後面增加多少位數字,二進位永遠只能以2來分割數軸,而十進位則是以10來分割數軸。
讓我們回想一下小學的數學知識,在十進位中,如果要用有限小數來表示一個分數的值,那麼這個分數的分母(化簡之後)一定不能包含除了2和5以外的其他質因數,因為十進位以10來分割數軸,而10分解質因數的結果為2×5。舉個例子:1/8、1/10、1/25都可以換算成有限小數(分別是0.125、0.1、0.04),因為這些分數的分母分解質因數之後只包含2或者5(8=2×2×2、10=2×5、25=5×5),而當分母包含其他質因數時,例如1/3、1/7、1/18這些則無法用有限小數來表示(也就是俗話說的「除不盡」)。如果我們把這個規律套用到二進位上會怎麼樣呢?2本身就是一個質數,無法分解質因數了,因此在二進位中,如果要用有限小數來表示一個分數的值,那麼這個分數的分母一定只能包含2這一個質因數,換句話說,分母必須為2的冪(2、4、8、16、32……)。
好了,我們回頭看看開頭的題目,0.1換算成分數就是1/10,而1/10的分母是10,10並不是2的冪,因此,在二進位中並不能用有限小數來表示1/10這個值。事實上,如果將0.1轉換成二進位,我們會得到一個無限循環小數:0.000110011001100……看到這裡,很多人估計已經想明白了,沒錯,計算機的精度是有限的,並不能直接處理無限小數,對於無限小數必須要截短到某個位置把它變成有限小數,但截短之後這個數就不準了,必然就產生了一點誤差,而連續加10次會將這種誤差放大,當誤差被放大到一定程度時,計算的結果就會出問題了,於是我們就看到了開頭的那一幕。如果用十進位來類比的話,大家可以想像一下,1/3+1/3+1/3=1,但1/3隻能用無限循環小數來表示,即0.333333……,如果我們將它截短到某一位,假設截到0.333,那麼0.333+0.333+0.333=0.999,你看,同樣也會出問題。
問題的原因總算搞清楚了,不過感覺很坑爹啊,計算機居然算不準小數,但為什麼平時大家很少因此遇到問題呢?那是因為大多數用戶都不用編寫程序,但對於整天編寫程序的程序員來說,這樣的問題其實經常遇到。比如說,如果你在一段程序中需要讓計算機判斷0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1是否等於1,計算機會告訴你不等於1,坑爹吧?如果這段程序涉及到算錢,有時候就會錯得很離譜,因此有經驗的程序員在碰到小數計算的時候都會特別小心。當然,作為一般用戶我們平時根本不需要關心這樣的問題,不過計算機居然會算錯數,怎麼想都覺得挺奇妙的吧?
※手機支付安全嗎?怎麼防範?
※飛機為什麼在接近地面時拉起機頭復飛?
TAG:知識百科 |