今天,發(fā)生一件非常有趣的事情。
公司同事問了我一個(gè)問題:為什么 2.0 - 1.1 = 0.89999999 呢?不應(yīng)該是 0.9嗎?
原來是,他問了周圍一圈的同事,都給他的是同一個(gè)回答,說這是精度問題。他百思不得其解,怎么就會(huì)產(chǎn)生精度問題呢。再問,就沒人知道原因了。
然后,我就看到了他抱著一本厚厚的書在看。拿過來一看,是一本Java書,厚厚的六百多頁,這還僅是第一卷。喲呵,這是準(zhǔn)備大干一場啊。
看在他這么努力學(xué)習(xí)的份上,還有他那對(duì)知識(shí)極度渴望的眼神。我決定,把我畢生所學(xué)傳授與他。
于是,就給他詳細(xì)講解了,計(jì)算機(jī)中是怎么存儲(chǔ)一個(gè)數(shù)的,十進(jìn)制是怎么在轉(zhuǎn)二進(jìn)制的過程中丟失精度的,以及浮點(diǎn)數(shù)是怎么遵循IEEE 754
規(guī)范的,在浮點(diǎn)數(shù)進(jìn)行加減運(yùn)算的過程中會(huì)經(jīng)歷對(duì)階、移位運(yùn)算等過程,以及在此過程中是怎么丟失精度的。(這些問題在之前的文章中都有解答,參看“為什么0.1+0.2=0.30000000000000004”)
然后,成功的把他徹底搞懵逼了。怎么這么難啊。
原來,他的計(jì)算機(jī)基礎(chǔ)比我還匱乏,不知道什么是位運(yùn)算,不知道什么是原碼、反碼和補(bǔ)碼。
本著我的熱心腸,我就給他普及了一下這些知識(shí) ---- 負(fù)數(shù)的補(bǔ)碼形式和位移運(yùn)算。
我們知道,一個(gè)數(shù)分為有符號(hào)和無符號(hào)。對(duì)于,有符號(hào)的數(shù)來說,最高位代表符號(hào)位,即最高位1代表負(fù)數(shù),0代表正數(shù)。
在計(jì)算機(jī)中,存儲(chǔ)一個(gè)數(shù)的時(shí)候,都是以補(bǔ)碼的形式存儲(chǔ)的。而正數(shù)和負(fù)數(shù)的補(bǔ)碼表示方式是不一樣的。正數(shù)的補(bǔ)碼就等于它的原碼,而負(fù)數(shù)的補(bǔ)碼是原碼除符號(hào)位以外都取反,然后
+ 1 得來的。以一個(gè)int類型為例(4個(gè)字節(jié)即32位)
14的原碼為:
0000 0000 0000 0000 0000 0000 0000 1110
它的反碼、補(bǔ)碼和原碼都是一樣的。
-14的原碼為:
//最高位1為符號(hào)位,代表此數(shù)為負(fù)數(shù) 1000 0000 0000 0000 0000 0000 0000 1110
反碼為原碼除了符號(hào)位以外的其他位都取反(即0變?yōu)?,1變?yōu)?),
1111 1111 1111 1111 1111 1111 1111 0001
補(bǔ)碼為反碼 + 1 ,注意二進(jìn)制中是滿二進(jìn)一。
1111 1111 1111 1111 1111 1111 1111 0010
位的左移,右移運(yùn)算就是分別向左和向右移動(dòng)N位。移位的規(guī)則是:
* 不管有沒有符號(hào)位,左移都是在低位補(bǔ)0
* 帶符號(hào)右移,是在高位補(bǔ)符號(hào)位,即正數(shù)補(bǔ)0,負(fù)數(shù)補(bǔ)1
* 無符號(hào)右移,無論該數(shù)是正數(shù)還是負(fù)數(shù)都在高位補(bǔ)0
因左移就在右邊低位補(bǔ)0就可以了,比較簡單,我就以負(fù)數(shù)的右移來舉例,是怎么計(jì)算無符號(hào)右移和帶符號(hào)右移的。還是以 -14 為例。
// -14的補(bǔ)碼 1111 1111 1111 1111 1111 1111 1111 0010 // 帶符號(hào)右移用 >> 表示,即右移一位
-14>>1,高位補(bǔ)符號(hào)位1,低位舍去 1111 1111 1111 1111 1111 1111 1111 1001 // 無符號(hào)右移用 >>>
表示,即右移一位 -14>>>1,最高位補(bǔ)0 0111 1111 1111 1111 1111 1111 1111 1001
我們可以通過程序來驗(yàn)證一下 -14>>1和 -14>>>1的結(jié)果是否正確。
1. -14>>1 = -7
//我們算出來 -14>>1的補(bǔ)碼為: 1111 1111 1111 1111 1111 1111 1111 1001 //那它具體代表的數(shù)值是多少呢?
//首先,補(bǔ)碼 -1 得到反碼 1111 1111 1111 1111 1111 1111 1111 1000 //然后,反碼取反得到原碼,最高位符號(hào)位不變
1000 0000 0000 0000 0000 0000 0000 0111
這結(jié)果不就是 -7 嗎,然后通過程序計(jì)算一下結(jié)果:
public class TestMove { public static void main(String[] args) {
System.out.println( -14>>1); } }
結(jié)果同樣也是-7 。說明了我們位移操作沒問題。
2. -14>>>1=2147483641
我們通過一段程序去驗(yàn)證:
package com.test.binary; ? /** * @Author zwb * @DATE 2019/12/3 15:49 */ public
class TestBinary { public static void main(String[] args) { //我們自己計(jì)算出來的 -14>>>1
結(jié)果 String bin = "01111111111111111111111111111001"; double res = binToDec(bin);
System.out.println(res); //通過計(jì)算機(jī)計(jì)算的結(jié)果 System.out.println(-14>>>1); } ?
//二進(jìn)制轉(zhuǎn)為十進(jìn)制 public static double binToDec(String bin){ int index =
bin.indexOf("."); int len = bin.length(); double res = 0; //index為-1說明沒有小數(shù)
if(index == -1){ for(int i = 0; i< len; i++){ res += Math.pow(2,i) *
Integer.parseInt(String.valueOf(bin.charAt(len-1-i))); } }else{ //整數(shù)部分 int
partA = 0; for(int i = 0; i< index; i++){ partA += Math.pow(2,i) *
Integer.parseInt(String.valueOf(bin.charAt(index-1-i))); } //小數(shù)部分 double partB
= 0; for(int j = index + 1; j < len; j++){ partB += Math.pow(2,index - j) *
Integer.parseInt(String.valueOf(bin.charAt(j))); } res = partA + partB; }
return res; } }
運(yùn)行之后的結(jié)果,可以在控制臺(tái)打印看到:
上邊第一個(gè)是我們自己通過推算它的補(bǔ)碼,然后通過二進(jìn)制轉(zhuǎn)十進(jìn)制的一個(gè)算法算出來的最終結(jié)果,第二個(gè)就是直接通過位運(yùn)算算出來的結(jié)果。可以看到結(jié)果是一模一樣的。
至此,是不是對(duì)原碼,反碼,補(bǔ)碼以及位運(yùn)算左移右移,有了比較清晰的認(rèn)識(shí)了呢?
熱門工具 換一換