計算機浮點數和存儲和運算規則
1、概述:
眾所周知,計算機只能識別二進制數據,即所有的十進制都需要轉換成二進制才能在計算機中進行存儲和運算,但是,十進制數有整數部分和小數部分,對於整數部分轉換成二進制數的話,我們采用除2取余數法;小數部分的話我們采用乘2取整法;求出來后,我們對數字進行規范化處理;
2、來個例子:
把十進制小數6.36轉換成二進制,具體怎么操作?
上述例子我們把一個帶小數的的十進制轉換成了二進制:但是有沒有發現一個問題?就是小數部分是一個無限循環的,我們計算機一個存儲單元可是存不下這么多數據的呀,雖然可以跨存儲區,但是無限的二進制,計算機是存不下的,不可能你定義一個數,結果電腦內存或硬盤立馬塞滿導致死機吧?那怎么辦呢?進行規范化處理
3、規范化處理
規范化處理是只將一個浮點數轉換成二進制后,根據存儲規則,通過符號位+階碼+尾數的形式表示該浮點數;
data = (-1)^s*M*2*E
在Java語言中,存儲浮點數主要有兩種基本數據類型:Float和Double
Float:通過Float定義的變量,占4個字節的存儲空間,即二進制數的長度為32位
Double:通過Double定義的變量,占8個字節的存儲空間,即二進制數的長度位64位
注意:計算機表示浮點數時,是用8位或者11位去存儲指數部分,在8位指數位數值上面,表示0~255,但是我們同樣需要有負指數,正負指數的位數量為了均等,各自一半,-127~128,0是特殊點,特殊處理。儲存時候會加上127,這樣就剛剛好是0~255;如果 E 為 11 位,它的取值范圍為 0~2047,中間數為1023;這樣就能很好的儲存了,不然的話,需要判斷符號位來判斷數值的正負,如下圖32位的長度表示浮點數時,我們可以得知當E+127>127則代表該數的階碼為正的,如果E+127<127則代表該數的階碼為負的,即01111111為正負數階碼的分隔值。
4、浮點數的計算
不管任何一門計算機語言,在進行浮點數的計算時都會出現精度問題,下面來看一下Java和JS語言計算浮點數案例:
public class test { public static void main(String[] args) { System.out.println(2.0F-1.5F); System.out.println(2.0F-1.3F); } } 執行結果: 0.5 0.70000005
結論:
我們可以看到,執行結果和我們預期的十進制的計算結果不一樣,讓我來分析一下:在這里需要大家有點原碼/反碼/補碼的知識;因為計算機不能像十進制一樣識別減法操作,所以任何減法都要轉換成補碼后再相加,比如:A-B = [A]補+[-B]補,正數的原碼反碼補碼都一樣:
2.0F - 1.5F
十進制:2.0F 二進制:10.0 規范化:我們在此忽略符號位
原碼:1.0000 0000 0000 0000 0000 000*2^1 //因為我們定義變量是Float類型,有效數據位為23位,所以需要補零讓長度為23
補碼:1.0000 0000 0000 0000 0000 000*2^1
十進制:1.5F
二進制:1.1
規范化:
原碼:1.1000 0000 0000 0000 0000 000*2^0 反碼:0.0111 1111 1111 1111 1111 111*2^0 //符號位不變,按位取反
補碼:0.1000 0000 0000 0000 0000 000*2^0 //補碼+1
計算:低階向高階看齊,即讓2^0向2^1看齊,小數點左移一位則乘以2^1; 01.00000000000000000000000*2^1 + 11.01000000000000000000000*2^1
——————————————————————————————————————
00.01000000000000000000000*2^1 //左邊第一個1為符號位溢出去除
所以2.0F-1.5F結果: 0.01000000000000000000000*2^1 = 0.10000000000000000000000*2^0
0.5F的浮點數表示:0.01000000000000000000000*2^0
所以2.0F-1.5F = 0.5F
驚不驚喜?意不意外?那為何2.0F-1.3F會不正確呢?
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
采用相同的分析方法:
2.0F - 1.3F
十進制:2.0F
二進制:10.0
規范化:1.00000000000000000000000*2^1
十進制:1.3F
二進制:1.0100 1100 1100 1100 1100 11001
規范化:1.0100 1100 1100 1100 1100 110*2^0 (23位)
計算:
1.00000000000000000000000*2^1
- 0.10100110011001100110011*2^1 //這里補碼我就不求了,操作和上面求補碼一樣
——————————————————————————————————————————————————————————————————————————————————————
0.01011001100110011001101*2^1
對結果進行規范化:
1.01100110011001100110100*2^-1
0.7的表示:
二進制:0.101110011001100110011001100 (規范化處理時存在一個細節:0舍1入),因為是Float類型,所以規范化處理后,要保留23個有效位即小數點后要有23個數據,但是第24是1所以要往前+1;如果是0則直接舍去;
規范化:1.011100110011001100110011*2^-1
所以最終結果並不相等
5、Java中如何對浮點數進行計算?
通過以上的案例分析,我們發現浮點數計算會存在誤差,比如我們去銀行里面取錢,我們賬戶上有2塊錢,我們取出來1.3,如果直接通過浮點數進行計算,則我們賬戶還剩余0.70000005;那這個銀行可能就在不知不覺中破產了,所有的程序員和工程師拉去坐牢去.......
那該怎么解決呢?
Java 提供了一個類對浮點數進行精准運算,BigDecimal對象,該類定義了很多可靠的方法,比如計算浮點數時向上取整或者向下取整,保留有效位數等等:
public class test { public static void main(String[] args) { BigDecimal BD1 = new BigDecimal("2.0"); BigDecimal BD2 = new BigDecimal("1.4"); System.out.println(BD1.subtract(BD2)); } } 運行結果: 0.6