【轉載】計算機程序的思惟邏輯 (5) - 小數計算爲何會出錯?

違反直覺的事實編程

計算機之因此叫"計算"機就是由於發明它主要是用來計算的,"計算"固然是它的特長,在你們的印象中,計算必定是很是準確的。但實際上,即便在一些很是基本的小數運算中,計算的結果也是不精確的。編程語言

好比:編碼

float f = 0.1f*0.1f;
System.out.println(f);

這個結果看上去,不言而喻,應該是0.01,但實際上,屏幕輸出倒是0.010000001,後面多了個1。code

看上去這麼簡單的運算,計算機怎麼會出錯了呢?blog

簡要答案ci

實際上,不是運算自己會出錯,而是計算機根本就不能精確的表示不少數,好比0.1這個數。數學

計算機是用一種二進制格式存儲小數的,這個二進制格式不能精確表示0.1,它只能表示一個很是接近0.1但又不等於0.1的一個數。it

數字都不能精確表示,在不精確數字上的運算結果不精確也就不足爲奇了。table

0.1怎麼會不能精確表示呢?在十進制的世界裏是能夠的,但在二進制的世界裏不行。在說二進制以前,咱們先來看下熟悉的十進制。class

實際上,十進制也只能表示那些能夠表述爲10的多少次方和的數,好比12.345,實際上表示的:1*10+2*1+3*0.1+4*0.01+5*0.001,與整數的表示相似,小數點後面的每一個位置也都有一個位權,從左到右,依次爲 0.1,0.01,0.001,...即10^(-1), 10^(-2), 10^(-3)。

不少數,十進制也是不能精確表示的,好比1/3, 保留三位小數的話,十進制表示是0.333,但不管後面保留多少位小數,都是不精確的,用0.333進行運算,好比乘以3,指望結果是1,但實際上倒是0.999。

二進制是相似的,但二進制只能表示哪些能夠表述爲2的多少次方和的數,來看下2的次方的一些例子:

2的次方 十進制
2^(-1) 0.5
2^(-2) 0.25
2^(-3) 0.125
2^(-4) 0.0625

能夠精確表示爲2的某次方之和的數能夠精確表示,其餘數則不能精確表示。

爲何必定要用二進制呢?

爲何就不能用咱們熟悉的十進制呢?在最最底層,計算機使用的電子元器件只能表示兩個狀態,一般是低壓和高壓,對應0和1,使用二進制容易基於這些電子器件構建硬件設備和進行運算。若是非要使用十進制,則這些硬件就會複雜不少,而且效率低下。

爲何有的小數計算是準確的

若是你編寫程序進行試驗,你會發現有的計算結果是準確的。好比,我用Java寫:

System.out.println(0.1f+0.1f); 
System.out.println(0.1f*0.1f);

第一行輸出0.2,第二行輸出0.010000001。按照上面的說法,第一行的結果應該也不對啊?

其實,這只是Java語言給咱們形成的假象,計算結果其實也是不精確的,可是因爲結果和0.2足夠接近,在輸出的時候,Java選擇了輸出0.2這個看上去很是精簡的數字,而不是一箇中間有不少0的小數。

在偏差足夠小的時候,結果看上去是精確的,但不精確其實才是常態。

怎麼處理計算不精確

計算不精確,怎麼辦呢?大部分狀況下,咱們不須要那麼高的精度,能夠四捨五入,或者在輸出的時候只保留固定個數的小數位。

若是真的須要比較高的精度,一種方法是將小數轉化爲整數進行運算,運算結束後再轉化爲小數,另外的方法通常是使用十進制的數據類型,這個沒有統一的規範,在Java中是BigDecimal,運算更準確,但效率比較低,本節就不詳細說了。

二進制表示

咱們以前一直在用"小數"這個詞表示float和double類型,其實,這是不嚴謹的,"小數"是在數學中用的詞,在計算機中,咱們通常說的是"浮點數"。float和double被稱爲浮點數據類型,小數運算被稱爲浮點運算。

爲何要叫浮點數呢?這是因爲小數的二進制表示中,表示那個小數點的時候,點不是固定的,而是浮動的。

咱們仍是用10進制類比,10進制有科學表示法,好比123.45這個數,直接這麼寫,就是固定表示法,若是用科學表示法,在小數點前只保留一位數字,能夠寫爲1.2345E2即1.2345*(10^2),即在科學表示法中,小數點向左浮動了兩位。

二進制中爲表示小數,也採用相似的科學表示法,形如 m*(2^e)。m稱爲尾數,e稱爲指數。指數能夠爲正,也能夠爲負,負的指數表示哪些接近0的比較小的數。在二進制中,單獨表示尾數部分和指數部分,另外還有一個符號位表示正負。

幾乎全部的硬件和編程語言表示小數的二進制格式都是同樣的,這種格式是一個標準,叫作IEEE 754標準,它定義了兩種格式,一種是32位的,對應於Java的float,另外一種是64位的,對應於Java的double。

32位格式中,1位表示符號,23位表示尾數,8位表示指數。64位格式中,1位表示符號,52位表示尾數,11位表示指數。

在兩種格式中,除了表示正常的數,標準還規定了一些特殊的二進制形式表示一些特殊的值,好比負無窮,正無窮,0,NaN (非數值,好比0乘以無窮大)。

IEEE 754標準有一些複雜的細節,初次看上去難以理解,對於平常應用也不經常使用,本文就不介紹了。

若是你想查看浮點數的具體二進制形式,在Java中,可使用以下代碼:

Integer.toBinaryString(Float.floatToIntBits(value))
Long.toBinaryString(Double.doubleToLongBits(value));

小結

小數計算爲何會出錯呢?理由就是:不少小數計算機中不能精確表示。

計算機的基本思惟是二進制的,因此,意料以外,情理之中!

上節咱們說了整數的二進制,本節談了小數。

那字符和文本呢?編碼是怎麼回事?亂碼又是什麼緣由?

相關文章
相關標籤/搜索