程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 計算機程序的思維邏輯 (5),思維邏輯

計算機程序的思維邏輯 (5),思維邏輯

編輯:JAVA綜合教程

計算機程序的思維邏輯 (5),思維邏輯


違反直覺的事實

計算機之所以叫"計算"機就是因為發明它主要是用來計算的,"計算"當然是它的特長,在大家的印象中,計算一定是非常准確的。但實際上,即使在一些非常基本的小數運算中,計算的結果也是不精確的。

比如:

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

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

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

簡要答案

實際上,不是運算本身會出錯,而是計算機根本就不能精確的表示很多數,比如0.1這個數。

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

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

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

實際上,十進制也只能表示那些可以表述為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));

小結

小數計算為什麼會出錯呢?理由就是:很多小數計算機中不能精確表示。

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

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

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

---------------- 

未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),深入淺出,老馬和你一起探索Java編程及計算機技術的本質。原創文章,保留所有版權。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved