在 Github 項(xiàng)目mongo-java-driver
有一個(gè)類ObjectId.java
,它的作用是生成唯一 id 的,它的核心實(shí)現(xiàn)是下面這樣一段代碼 [1]:
public void putToByteBuffer(final ByteBuffer buffer) {notNull("buffer", buffer);
isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
buffer.put(int3(timestamp));
buffer.put(int2(timestamp));
buffer.put(int1(timestamp));
buffer.put(int0(timestamp));
buffer.put(int2(randomValue1));
buffer.put(int1(randomValue1));
buffer.put(int0(randomValue1));
buffer.put(short1(randomValue2));
buffer.put(short0(randomValue2));
buffer.put(int2(counter));
buffer.put(int1(counter));
buffer.put(int0(counter));
}
上述代碼中的int2()
方法定義如下:
private static byte int2(int x) {return (byte) (x >>16);
}
取當(dāng)前時(shí)間戳(秒)1658385462,我們來測(cè)試一下該方法:
@Test
public void test() {System.out.println(int2(1658385462)); // -40
}
得到的結(jié)果是 -40。即:(byte) 1658385462 >>16 = -40
。
這是怎么算出來的?
計(jì)算過程1、首先,計(jì)算機(jī)要將 1658385462 轉(zhuǎn)換為二進(jìn)制數(shù)。
因?yàn)?int 為 4 字節(jié) 32 位,對(duì)應(yīng)二進(jìn)制結(jié)果如下:
0110 0010 1101 1000 1111 0100 0011 0110
2、執(zhí)行 >>16 運(yùn)算。
運(yùn)算結(jié)果是 0110 0010 1101 1000。
0110 0010 1101 1000 1111 0100 0011 0110 >>16 = 0110 0010 1101 1000
3、因?yàn)橛?jì)算機(jī)存儲(chǔ)補(bǔ)位,所以需將其轉(zhuǎn)為補(bǔ)位。
正數(shù)的補(bǔ)碼就是其本身,補(bǔ)碼是:0110 0010 1101 1000。
4、因?yàn)?byte 為 1 字節(jié) 8 位,所以強(qiáng)制轉(zhuǎn)換時(shí)計(jì)算機(jī)只保留其后 8 位。
保留 8 位的結(jié)果是:1101 1000。
5、保留 8位后的數(shù)值仍然是補(bǔ)位,而要展示給用戶需轉(zhuǎn)換成原位。
補(bǔ):1101 1000
反:1101 0111
原:1010 1000
6、最高位 1 表示負(fù)數(shù),將 010 1000 轉(zhuǎn)換成十進(jìn)制數(shù),則為 -40。
什么是原碼、反碼、補(bǔ)碼?原碼:原碼就是符號(hào)位加上真值的絕對(duì)值,即用第一位表示符號(hào),其余位表示值。
反碼:正數(shù)的反碼是其本身。負(fù)數(shù)的反碼是在其原碼的基礎(chǔ)上,符號(hào)位不變,其余各位取反。
補(bǔ)碼:正數(shù)的補(bǔ)碼就是其本身。負(fù)數(shù)的補(bǔ)碼是在其原碼的基礎(chǔ)上,符號(hào)位不變,其余各位取反,最后+1。
從原碼、反碼、補(bǔ)碼的表示方式不難看出,原碼才是人眼最直觀能看出值的表示方式,那么為什么還要有反碼和補(bǔ)碼呢?
答案是為了簡(jiǎn)化計(jì)算機(jī)集成電路的設(shè)計(jì)。
我們?nèi)四X是可以辨別第一位是符號(hào)位的,在計(jì)算的時(shí)候我們會(huì)根據(jù)符號(hào)位,選擇對(duì)真值區(qū)域的加減。但是對(duì)于計(jì)算機(jī),辨別“符號(hào)位”顯然會(huì)讓計(jì)算機(jī)的基礎(chǔ)電路設(shè)計(jì)變得十分復(fù)雜,于是人們想出了將符號(hào)位也參與運(yùn)算的方法。
我們知道,根據(jù)運(yùn)算法則:減去一個(gè)正數(shù)等于加上一個(gè)負(fù)數(shù),即:1-1 = 1 + (-1) = 0,所以機(jī)器可以只有加法而沒有減法,這樣計(jì)算機(jī)運(yùn)算的設(shè)計(jì)就更簡(jiǎn)單了。此外,由于現(xiàn)階段計(jì)算機(jī) CPU 擅長(zhǎng)做加法運(yùn)算,CPU 硬件實(shí)現(xiàn)減法要復(fù)雜得多,而且運(yùn)算效率很低,所以我們偷懶只討論加法運(yùn)算。說不定以后發(fā)明了減法加速硬件,那就另當(dāng)別論了。
為什么要有反碼?于是人們開始探索將符號(hào)位參與運(yùn)算,并且只保留加法的方法。 首先來看原碼:計(jì)算十進(jìn)制的表達(dá)式:1-1=0。
1 - 1
= 1 + (-1) = [00000001]原 + [10000001]原
= [10000010]原
= -2
如果用原碼表示,讓符號(hào)位也參與計(jì)算,顯然對(duì)于減法來說,結(jié)果是不正確的。這也就是為何計(jì)算機(jī)內(nèi)部不使用原碼表示一個(gè)數(shù)。
為了解決原碼做減法的問題,出現(xiàn)了反碼。
1 - 1
= 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [0000 0001]反 + [1111 1110]反
= [1111 1111]反
= [1000 0000]原
= -0
發(fā)現(xiàn)用反碼計(jì)算減法,結(jié)果的真值部分是正確的。
為什么要有補(bǔ)碼?用反碼計(jì)算減法,結(jié)果的真值部分是正確的。而唯一的問題其實(shí)就出現(xiàn)在“0”這個(gè)特殊的數(shù)值上。 雖然人們理解上 +0 和 -0 是一樣的,但是 0 帶符號(hào)是沒有任何意義的。 而且會(huì)有[0000 0000]原
和[1000 0000]原
兩個(gè)編碼表示 0。
于是出現(xiàn)了補(bǔ)碼,為了解決 0 的符號(hào)以及 0 的兩個(gè)編碼問題:
1 - 1
= 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [0000 0001]補(bǔ) + [1111 1111]補(bǔ)
= [0000 0000]補(bǔ)
= [0000 0000]原
這樣 0 用 [0000 0000] 表示,而以前出現(xiàn)問題的 -0 則不存在了。那另一個(gè)編碼 [1000 0000] 是否就棄用了呢?
(-1) + (-127)
= [1000 0001]原 + [1111 1111]原
= [1111 1111]補(bǔ) + [1000 0001]補(bǔ)
= [1000 0000]補(bǔ)
-1-127 的結(jié)果應(yīng)該是 -128,剛好 [1000 0000] 可以用來表示 -128。在用補(bǔ)碼運(yùn)算的結(jié)果中,[1000 0000]補(bǔ)
就是 -128。
但是注意: -128 并沒有原碼和反碼表示。對(duì) -128 的補(bǔ)碼[1000 0000]補(bǔ)
算出來的原碼是[0000 0000]原
,這顯然是不正確的。
使用補(bǔ)碼,不僅僅修復(fù)了 0 的符號(hào)以及存在兩個(gè)編碼的問題,而且還能夠多表示一個(gè)最低數(shù)。所以最終同樣是 8 位二進(jìn)制,使用原碼或反碼表示的范圍為 [-127, +127],而使用補(bǔ)碼表示的范圍為 [-128, 127]。
小結(jié)我整理了本文知識(shí)消化鏈路,如下。
使用原碼計(jì)算減法的結(jié)果是錯(cuò)誤的
->出現(xiàn)了反碼
->使用反碼計(jì)算的 0 有兩個(gè),+0 和 -0
->出現(xiàn)了補(bǔ)碼
更多技術(shù)干貨,敬請(qǐng)關(guān)注公眾號(hào)「楊同學(xué)technotes」,歡迎技術(shù)交流。
文中提及的鏈接
- [1] ObjectId#putToByteBuffer
參考資料
- 計(jì)算機(jī)為什么要使用原碼、反碼、補(bǔ)碼
- java中int強(qiáng)制轉(zhuǎn)byte數(shù)據(jù)溢出問題
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧