違反直覺的事實編程
計算機之因此叫"計算"機就是由於發明它主要是用來計算的,"計算"固然是它的特長,在你們的印象中,計算必定是很是準確的。但實際上,即便在一些很是基本的小數運算中,計算的結果也是不精確的。編程語言
好比:編碼
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));
小結
小數計算爲何會出錯呢?理由就是:不少小數計算機中不能精確表示。
計算機的基本思惟是二進制的,因此,意料以外,情理之中!
上節咱們說了整數的二進制,本節談了小數。
那字符和文本呢?編碼是怎麼回事?亂碼又是什麼緣由?