1. 程式人生 > 實用技巧 >跟光磊學Java開發-深入理解整數儲存和位運算

跟光磊學Java開發-深入理解整數儲存和位運算

跟光磊學Java開發-深入理解整數儲存和位運算

跟光磊學Java開發

計算機進位制及其轉換

計算機進位制介紹

進位制的定義:進位制是一種計數方式,也稱為進位計數法或者位值計數法,使用有限數字符號表示無限的數值,使用的數字符號的數目稱為這種進位制的基數或者底數,例如十進位制就是由0-9十個數字組成。在計算機記憶體中,都是以二進位制的補碼形式來儲存資料的,生活中以十進位制方式計算的資料居多,例如賬戶餘額,開發人員的薪水等等。而 計算的記憶體地址、MAC地址等等通常都是使用十六進位制表示的,Linux的許可權系統採用八進位制的資料表示的。相同進位制型別資料進行運算時會遵守加法:逢R進1

;減法:**借1當R,**其中R就表示進位制。

計算機常用進位制的組成、示例和使用場景

進位制名稱 組成 數值示例 應用場景
二進位制 0,1 1010 計算機內部資料表示
八進位制 0,1,2,3,4,5,6,7 010 Linux許可權系統
十進位制 01,2,3,4,5,6,7,8,9 12 整數
十六進位制 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f 12f 資料的記憶體地址

十進位制、二進位制、八進位制、十六進位制的對應關係

十進位制 二進位制 八進位制 十六進位制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 11111 17 F
16 10000 20 10

計算機底層為什麼只能識別二進位制

生活中使用的是十進位制,而計算機採用的二進位制,但是由於用二進位制表示資料太長,不方便閱讀,因此引入了八進位制、十六進位制。

我們目前主要使用的計算機都是大規模積體電路,是採用大規模和超大規模的積體電路作為邏輯元件的。積體電路按其功能、結構的不同,可以分為模擬積體電路、數字積體電路和數/模混合積體電路三大類。而我們的計算機主要是採用數字積體電路搭建的。邏輯閘是數字邏輯電路的基本單元。常見的邏輯閘包括“與”門,“或”門,“非”門,“異或”等等。通過邏輯閘可以組合使用實現更為複雜的邏輯運算和數值運算。邏輯閘可以通過控制高、低電平,從而實現邏輯運算。電源電壓大小的波動對其沒有影響,溫度和工藝偏差對其工作的可靠性影響也比類比電路小得多,所以相對穩定。因為數字計算機是由邏輯閘組成,而邏輯電路最基礎的狀態就是兩個:開和關。所以,數位電路是以二進位制邏輯代數為數學基礎。二進位制的基本運算規則簡單,運算操作方便,這樣一來有利於簡化計算機內部結構,提高運算速度。但是在日常開發中,通常都會使用八進位制和十六進位制,因為八進位制和十六進位制相對於二進位制表示資料更加簡潔,而且一個八進位制表示三個二進位制,一個十六進位制表示四個二進位制。例如1024使用二進位制表示為0b100 0000 0000,使用八進位制表示為02000,使用十六進位制表示為0x400。

十進位制轉二進位制、八進位制、十六進位制

十進位制轉換二進位制、八進位制、十六進位制可以採用短除法,即待轉換的十進位制數除以指定的進位制(例如2,8,16),直到商數為0,求餘數。

十進位制101轉換為二進位制的計算過程

重複除以2 商數 餘數
101/2 50 1
50/2 25 0
25/2 12 1
12/2 6 0
6/2 3 0
3/2 1 1
1/2 0 1

然後將餘數的結果從下到上串聯起來的結果:1100101,即十進位制的101轉換為二進位制的結果為1100101

十進位制的237轉換為二進位制

重複除以2 商數 餘數
237/2 118 1
118/2 59 0
59/2 29 1
29/2 14 1
14/2 7 0
7/2 3 1
3/2 1 1
1/2 0 1

然後將餘數的結果從下到上串聯起來的結果:11101101,即十進位制的237轉換為二進位制的結果為11101101。

除了短除法以外,如果掌握了常見的十進位制數對應的二進位制數

科學計數法 十進位制 二進位制
2^0 1 1
2^1 2 10
2^2 4 100
2^3 8 1000
2^4 16 10000
2^5 32 100000
2^6 64 1000000
2^7 128 10000000
2^8 256 100000000
2^9 512 1000000000
2^10 1024 10000000000

在進行十進位制轉二進位制時,還可以使用減法來轉換
以十進位制11轉換為二進位制為例,因為11介於16和8中間,而11-8=3,8的二進位制表示為1000,3的二進位制是11,而二進位制的1000+11=1011,因此十進位制的11轉換為二進位制的結果是1011

Windows系統中可以使用計算器(calc)來實現進位制之間的轉換

二進位制、八進位制、十六進位制轉十進位制

首先明確不同進位制的值是如何計算的,這裡以十進位制和二進位制為例子,闡述它們的計算過程。

十進位制整數1024

1024=1*10^3+2*10^1+4*10^0=1000+20+4=1024

二進位制整數 10000000000

10000000000 =1*2^10=1024

二進位制、八進位制、十六進位制整數轉十進位制整數是使用按權展開法計算的,這裡以二進位制資料01100101為例子。從右往左開始數,如果二進位制位為1,則依次用1*2^n,n從0開始,依次遞增。

二進位制整數01100101 轉換為十進位制整數的計算過程

01100101=2^6+2^5+2^2+2^0=64+32+4+1=101

八進位制整數0127轉換為十進位制整數的計算過程

0127=1*8^2+2*8^1+7=64+16+7=87

十六進位制整數0x12f轉換為十進位制整數的計算過程

0x12f=1*16^2+2*16^1+f*16^0=256+32+15=303

二進位制的1011轉換為十進位制的計算過程

1011=1000+11=2^3+2^1+2^0=8+2+1=11

二進位制轉八進位制、十六進位制

二進位制轉八進位制是按照從右往左,每3位二進位制對應1位八進位制,不足補零,因為2的3次方等於8

二進位制整數11001100轉八進位制計算過程

11 001 100 =0314

二進位制轉十六進位制是按照從右往左,每4位二進位制對應1位十六進位制,不足補零,因為2的4次方等於16。

二進位制整數1100 1110轉十六進位制計算過程

1100 1110 =0xce

八進位制、十六進位制轉二進位制

八進位制轉二進位制是按照從右往左,每1位八進位制對應3位二進位制。

八進位制整數0127轉二進位制整數計算過程

0127=001 010 111

十六進位制轉二進位制是按照從右往左,每1位十六進位制對應4位二進位制。

十六進位制整數0x12f轉換為二進位制整數計算過程

0x12f=0001 0010 1111

Java的四種進位制整數常量

Java支援二進位制、八進位制、十進位制和十六進位制四種禁止的整數常量

  • 二進位制整數常量以0b開頭,包含0和1兩個數字組成
  • 八進位制整數常量以0開頭,包含0-7之間的八個數字組成
  • 十進位制是整數常量的預設進位制,包含0-9之間的十個數字組成
  • 十六進位制以0x開頭,包含0-9之間的十個數字以及a-f之間的6個字母組成

使用System.out.println()輸出整數常量時預設都是按照十進位制的結果輸出

package net.ittimeline.java.core.jdk.foundational.syntax;

/**
 * 四種進位制的整數常量
 *
 * @author tony [email protected]
 * @version 2020/12/12 7:47
 * @since JDK11
 */
public class IntConstants {

  public static void main(String[] args) {
    // 二進位制常量使用0b開頭,JDK7以後支援二進位制常量
    System.out.println("二進位制資料100轉換為十進位制的資料結果為" + 0b100);
    // 八進位制整數 常量以0開頭
    System.out.println("八進位制資料100轉換為十進位制的資料結果為" + 0100);
    // 整數常量預設是十進位制
    System.out.println("十進位制資料100" + 100);
    // 十六進位制整數常量以0x開頭,包含0-9之間的十個數字以及a-f之間的6個字母組成
    System.out.println("十六進位制資料0x100轉換為十進位制的資料結果為" + 0x100);
  }
}

程式執行結果

整數儲存和運算的機制

原碼、反碼、補碼

任何資料在計算機中是以補碼的方式儲存,而資料的表現形式有原碼、反碼和補碼。
正數的原碼,反碼和補碼都一致,而負數經歷了原碼->反碼->補碼的換算過程

  • 原碼
    原碼錶示資料本身的二進位制
    例如無符號數15以一個位元組的原碼錶示為0000 1111
    有符號 分為正數和負數,最高位表示符號位。正數15 以一個位元組的原碼錶示為 00001111,而負數-15以一個位元組的原碼錶示為1000 1111

  • 反碼
    無符號數 反碼等於原碼,以一個位元組的無符號整數15為例,其原碼為0000 1111,其反碼也是0000 1111
    有符號數 正數反碼等於原碼,負數就是最高位不變,其他位取反,以-15為例,其原碼為 1000 1111,反碼錶示為1111 0000

  • 補碼
    無符號數 補碼等於原碼
    有符號數 正數補碼等於原碼,負數就是反碼的最低位加1即可,以-15為例,原碼為 1000 1111 ,反碼錶示為1111 0000,補碼就是反碼加1,其結果是1111 0001。

綜上所述,無符號數以及有符號正數計算機存的是原碼,因為補碼等於原碼。而有符號負數計算機儲存的是補碼,補碼等於原碼取反加1。

原碼->反碼->補碼的演變過程(以byte型別說明)

首先明確 原碼的+0,+1和-0,-1表示方法
0的原碼是0000 0000 ,1的原碼是0000 0001
-0的原碼是1000 0000, -1的原碼是1000 0001

無符號正數,有符號正數的原碼,反碼和補碼都是一樣的

為什麼有了原碼還需要補碼,這裡以原碼計算1-1,因為計算器內部只有加法器,因為1-1 等價於1+ -1,因此
0000 0001+ 1000 0001=1000 0010 轉換成十進位制 等於-2 ,證明使用原碼儲存負數,在參與運算時得到的確是錯誤的結果。
除此以外,使用原碼錶示0有兩種表示方法:0000 0000 和1000 0000

因為原碼的負數運算結果錯誤,而且不能正確的表示0,因此計算機行業的大佬們開始嘗試使用反碼來儲存負數

首先明確 反碼的+0,+1和-0,-1表示方法
0的原碼是0000 0000,轉換為反碼是0000 0000 ,1的原碼是0000 0001,反碼是0000 0001
-0的原碼是1000 0000,轉換為反碼是1111 1111, -1的原碼是1000 0001,反碼是1111 1110

使用反碼計算 1+ -1 = 0000 0001+1111 1110=1111 1111 ,1111 1111剛好是-0的反碼,雖然負數使用反碼運算時,結果準確,但是0的表示方式依然還是有兩種:0000 0000 和1111 1111。

綜上所述 如果計算機使用反碼儲存負數,運算準確,但是0的反碼錶示方式還是有兩種。

由於反碼不能正確表示0的方式,計算機大佬們又開始想新的方式:補碼來儲存負數

首先明確 補碼的+0,+1和-0,-1表示方法
0的原碼是0000 0000,轉換為反碼是0000 0000,轉換為補碼是0000 0000 ,1的原碼是0000 0001,反碼是0000 0001,轉換為補碼是0000 0000
-0的原碼是1000 0000,轉換為反碼是1111 1111,轉換為補碼為0000 0000, -1的原碼是1000 0001,反碼是1111 1110,轉換為補碼是1111 1111

使用補碼計算 1+ -1= 0000 0001 + 1111 1111=0000 0000 轉換為十進位制的結果就是0,而且補碼+0和-0的二進位制表示方式都是0000 0000,因此補碼即解決了負數運算時的結果正確性,又保證了0只有一種0000 0000的表示方式。

這裡再總結下原碼、反碼、補碼三者之間的計算。

  1. 根據原碼求補碼
    根據原碼求補碼之前首先需要根據原碼求反碼,即原碼的最高位不變,其他位取反。求到反碼以後,就可以根據反碼的末位加1得到補碼。
  2. 根據補碼求原碼
    根據補碼求原碼之前首先需要根據補碼求反碼,即補碼末位減1得到反碼,然後反碼求原碼,也就是反碼符號位不變,其他位取反得到原碼。

其實這裡就可以看出反碼的作用,它就是用於原碼和補碼轉換的橋樑。
而補碼的使用場景是計算機資料在進行相關運算時(例如算術運算,賦值運算,位運算)使用的。
原碼的使用場景是計算技術資料在進行展示時(例如使用System.out.println("");列印輸出)使用的。

為什麼byte型別的範圍是-128到127?

因為byte佔據的位元組數量是1個位元組(Byte),1Byte=8bit,即最小值是0000 0000,最大值是1111 1111,但是有符號整數由正整數、0和負整數三部分組成。
正整數0的一個位元組表示方式為0000 0000
一個位元組表示最小的正整數 0000 0001
一個位元組表示最大的正整數 0111 1111 換算成十進位制就是127
一個位元組表示最小的負整數 1000 0001 ,此時1000 0001是補碼,還需要轉換為反碼和原碼,1000 0001 轉換為反碼的結果是1000 0000,1000 0000轉換為原碼的結果是1111 1111,1111 1111轉換為十進位制就是-127
一個位元組表示最大的負整數1111 11111,此時1111 1111是補碼,還需要轉換為反碼和原碼,11111111 轉換為反碼的結果是1111 1110,1111 1110轉換為原碼的結果就是 1000 0001,1000 0001轉換為十進位制就是-1。
負整數0的一個位元組表示方式為1000 0000,如果用1000 0000表示負0,那就浪費了,因為0不區分正負,因此用1000 0000表示-128。
1000 0000 怎麼計算是-128呢?
-127的二進位制補碼是1000 0001 ,1的二進位制補碼是0000 0001,-127 - 1=-128,因此 1000 0001 - 0000 0001 =1000 0000=-128。

整數溢位的記憶體原理

在使用對應資料型別時不要超過它們的範圍,如果超過了就會造成資料溢位錯誤,執行程式會得到一個錯誤的結果。

package net.ittimeline.java.core.jdk.foundational.syntax;

/**
 * 整數溢位的記憶體原理
 *
 * @author tony [email protected]
 * @version 2020/12/12 8:36
 * @since JDK11
 */
public class IntOverFlow {
  public static void main(String[] args) {
      //賦值時(修改變數的值) 賦的值是十進位制,給的是原碼,如果賦值給的是八進位制或者十六進位制,給的是補碼
      //列印輸出到終端(獲取變數的值)十進位制列印的是原碼,十六進位制或者八進位制列印輸出的是補碼

      /*

	    資料溢位的記憶體原理
		129預設是int型別,但是在賦值之前使用了強制型別轉換轉換為byte型別了
		1Byte=8bit
		129使用1個位元組的補碼錶示的方式是 1000 0001

		在列印輸出時需要轉換成原碼
		1000 0001 首先轉換成反碼
		補碼轉反碼就是補碼的末位減1 也就是1000 0001 -1 =1000 0000
		反碼轉換成原碼 最高位不變 其他位取反
		反碼1000 0000 轉換成原碼的結果是1111 1111
		列印輸出時預設是有符號的十進位制輸出,因此列印輸出結果是-127

	*/

      byte number=(byte)129;
    System.out.println("number = "+number);
  }
}

程式執行結果


程式執行結果

整數賦值溢位的原因剖析

賦值時(修改變數的值) 賦的值是十進位制,給的是原碼,如果賦值給的是八進位制或者十六進位制,給的是補碼
列印輸出到終端(獲取變數的值)十進位制列印的是原碼,十六進位制或者八進位制列印輸出的是補碼

129預設是int型別,佔據4個位元組,也就是32個二進位制位,但是在賦值之前使用了強制型別轉換轉換為byte型別了
1Byte=8bit,也就只能用8個二進位制位來表示129
129使用1個位元組的補碼錶示的方式是 1000 0001
在列印輸出時需要轉換成原碼
1000 0001 首先轉換成反碼
補碼轉反碼就是補碼的末位減1 也就是1000 0001 -1 =1000 0000
反碼轉換成原碼 最高位不變 其他位取反
反碼1000 0000 轉換成原碼的結果是1111 1111
列印輸出時預設是有符號的十進位制輸出,因此列印輸出結果是-127

位運算

位運算子概述

位運算是整數基於二進位制補碼的運算,其運算效率是最高的,但是可讀性不太好。因為人們生活中接觸十進位制比較多,而計算機底層都是二進位制的。
位運算子在進行運算時先將十進位制數轉換成二進位制再進行運算。
Java語言持7種位運算子,包括四種基本位運算子:按位與(&),按位或(|),按位異或(^)和按位取反(~)和三種移位運算子:按位左移(<<),按位右移(>>)以及無符號右移(>>>)

在Java原始碼中大量使用了基於二進位制補碼的位運算,但是在進行業務開發時很少用位運算子。
三種移位運算子在JDK集合原始碼中的使用
無符號右移

按位右移

按位左移

如何區分&,|,^是邏輯運算子還是位運算子? 如果操作的是boolean型別就是邏輯運算子,如果操作的是整數,那麼就是位運算子

四種基本位運算子的使用

  • 按位與(&):只有當&兩邊的二進位制補碼都為1時返回1,其餘返回0
    • 1&1=1
    • 1&0=0
    • 0&1=0
    • 0&0=0

按位與運算案例:255&15

255和15都是正整數 正整數原碼、反碼、補碼都一樣
255預設是十進位制,佔據4個位元組長度,轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 1111 1111
15預設是十進位制,佔據4個位元組長度,轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 0000 1111
1111 1111 & 0000 1111 =0000 1111,轉換為十進位制的結果是 15

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位與運算案例:255&15
 *
 * @author tony [email protected]
 * @version 2020/12/12 11:01
 * @since JDK11
 */
public class BitAndInt {

  public static void main(String[] args) {
    /*
        255和15都是正整數 正整數原碼、反碼、補碼都一樣

        255預設是十進位制,佔據4個位元組長度
        轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 1111 1111

        15預設是十進位制,佔據4個位元組長度
        轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 0000 1111

        1111 1111
   按位與(&)
        0000 1111
   =
        0000 1111
   轉換為十進位制的結果是 15
     */

    System.out.println("255 & 15 = "+(255&15));
  }
}

程式執行結果

  • 按位或(|):當|兩邊的二進位制補碼有一邊為1時返回1,其餘返回0
    • 1 | 1=1
    • 1| 0=1
    • 0 | 1=1
    • 0 | 0=0

按位或運算案例: 128|64

128和64都是正整數 正整數原碼、反碼、補碼都一樣
128預設是十進位制,佔據4個位元組長度,轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 1000 0000
64預設是十進位制,佔據4個位元組長度,轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 0100 0000
1000 0000 | 0100 0000=1100 0000 ,轉換為十進位制的結果是192

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位或運算案例 128&64
 *
 * @author tony [email protected]
 * @version 2020/12/12 11:13
 * @since JDK11
 */
public class BitOrInt {

  public static void main(String[] args) {
     /*
        128和64都是正整數 正整數原碼、反碼、補碼都一樣

        128預設是十進位制,佔據4個位元組長度
        轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 1000 0000

        64預設是十進位制,佔據4個位元組長度
        轉換為二進位制的結果是 0000 0000 0000 0000 0000 0000 0100 0000

        1000 0000
   按位或(|)
        0100 0000
   =
        1100 0000
   轉換為十進位制的結果是 192
     */

      System.out.println("128 | 64 = "+(128|64));
  }
}
  • 按位異或():當兩邊的二進位制補碼相同時返回0,不同時返回1
    • 1 ^ 1=0
    • 1 ^ 0=1
    • 0 ^ 1=1
    • 0 | 0 = 0

按位異或案例:256^256

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位異或運算案例 256&256
 *
 * @author tony [email protected]
 * @version 2020/12/12 11:20
 * @since JDK11
 */
public class BitXorInt {

  public static void main(String[] args) {
     /*
        256是正整數 正整數原碼、反碼、補碼都一樣

        256預設是十進位制,佔據4個位元組長度
        轉換為二進位制的結果是 0000 0000 0000 0000 0000 0001 0000 0000



        1 0000 0000
   按位與(|)
        1 0000 0000
   =
        0 0000 0000
   轉換為十進位制的結果是 0
     */

      System.out.println("256 ^ 256 = "+(256 ^ 256));
  }
}

程式執行結果

在進行異或運算時一個數被另一個數異或兩次,該數本身不變,例如10^5^5=10

因此異或可以拿來做變數交換,而且是最優解的演算法。

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 使用資料交換
 *
 * @author tony [email protected]
 * @version 2020/12/12 11:30
 * @since JDK11
 */
public class BitXorDataExchange {

  public static void main(String[] args) {

    int left = 10;
    int right = 20;
    System.out.printf("【異或運算】變數交換之前 left = %d  right = %d \n", left, right);

    left = left ^ right;
    right = left ^ right;
    left = left ^ right;
    System.out.printf("【異或運算】變數交換之後 left = %d  right = %d \n", left, right);
  }
}


程式執行結果

其他兩種交換資料的方法就是通過臨時變數以及算術運算。

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 資料交換的其他兩種方式
 *
 * @author tony [email protected]
 * @version 2020/12/12 11:33
 * @since JDK11
 */
public class DataExchange {

  public static void main(String[] args) {

      int left= 10;
      int right=20;

      dataExchangeArithmetic(left,right);

      dataExchangeTempSpace(left,right);
      
  }

    /**
     * 使用算術運算交換兩個變數
     * @param left
     * @param right
     */
  public static void dataExchangeArithmetic(int left,int right){
      System.out.printf("【算術運算】變數交換之前 left = %d  right = %d \n", left, right);
      left=left+right;
      right=left-right;
      left=left-right;
      System.out.printf("【算術運算】變數交換之後 left = %d  right = %d \n", left, right);

  }

    /**
     * 使用臨時變數交換兩個變數
     * @param left
     * @param right
     */
    public static void dataExchangeTempSpace(int left,int right){
        System.out.printf("【臨時空間】變數交換之前 left = %d  right = %d \n", left, right);
        int temp=left;
        left=right;
        right=temp;
        System.out.printf("【臨時空間】變數交換之後 left = %d  right = %d \n", left, right);

    }



}

程式執行結果

  • 按位取反(~) :單目運算子,即只能操作一個變數或則表示式,將運算元的每個位(包括符號位)全部取反
    • ~0=1
    • ~1=0

取反運算子案例: ~245 和~-245

~245的計算過程

~-245的計算過程

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位取反運算案例:~245
 * 按位取反(~) :單目運算子,即只能操作一個變數或則表示式,將運算元的每個位(包括符號位)全部取反
 * @author tony [email protected]
 * @version 2020/12/12 12:24
 * @since JDK11
 */
public class BitNotSignedInt {
    public static void main(String[] args) {

        /*
         ~245的計算過程
          245的二進位制補碼是0000 0000 0000 0000 0000 0000 1111 0101

          按位取反(包括符號位)
                         1111 1111 1111 1111 1111 1111 0000 1010
          取反的補碼結果是一個負數需要將其轉換成原碼輸出
          其反碼錶示方式是  1111 1111 1111 1111 1111 1111 0000 1001
          其原碼錶示方式是  1000 0000 0000 0000 0000 0000 1111 0110
          轉換為十進位制的結果是-246
         */

        System.out.println("十進位制有符號正整數245按位取反的結果是"+(~245));

        /*
        ~-245的計算過程
          -245的二進位制原碼是1000 0000 0000 0000 0000 0000 1111 0101
          其反碼錶示方式    1111 1111 1111 1111 1111 1111 0000 1010
          其補碼錶示方式    1111 1111 1111 1111 1111 1111 0000 1011
          按位取反(包括符號位)
                         0000 0000 0000 0000 0000 0000 1111 0100
          取反的補碼結果是一個正整數,轉換為十進位制的結果是244
         */

        System.out.println("十進位制有符號負整數-245按位取反的結果是"+(~-245));



    }
}

程式執行結果

三種移位運算的使用

  • 有符號左移(<<) :二進位制位向左移動,左邊符號位丟棄,右邊補齊0。有符號左移的規律:向左移動幾位,就是乘以2的幾次方

有符號左移案例: 10<<2和-10<<2

10<<2 計算過程

-10<<2計算過程


十進位制有符號正整數左移


package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符號左移案例: 10<<2和-10<<2
 * 正數、負數的按位左移
 * 二進位制位向左移動,左邊符號位丟棄,右邊補齊0。有符號左移的規律:向左移動幾位,就是乘以2的幾次方
 * @author tony [email protected]
 * @version 2020/12/12 11:38
 * @since JDK11
 */
public class BitLeftMoveSignedInt {
  public static void main(String[] args) {

      /*
       10 << 2的計算過程
       10預設是有符號的十進位制正整數
       轉換為二進位制 0000 0000 0000 0000 0000 0000 0000 1010
       向左邊移動兩位後 右邊補兩個0 左邊的兩個0丟棄
       00 0000 0000 0000 0000 0000 0000 101000
       101000 轉換為十進位制的結果是40
       因此 10 << 2 =40
       */
      System.out.println("有符號十進位制正整數10左移2位的結果是" + (10 << 2));

      /*
      -10 << 2 的計算過程
      -10預設是有符號的十進位制負整數
      -10原碼是         1000 0000 0000 0000 0000 0000 0000 1010
      在進行移位運算之前需要轉換為補碼
      原碼取反轉換為反碼  1111 1111 1111 1111 1111 1111 1111 0101
      反碼加1轉換為補碼   1111 1111 1111 1111 1111 1111 1111 0110

      1111 1111 1111 1111 1111 1111 1111 0110 向左移動兩位
      右邊補上2個0,左邊符號位丟棄
      11 1111 1111 1111 1111 1111 1111 011000
      輸出時需要將其轉換為原碼
      補碼 11 1111 1111 1111 1111 1111 1111 011000
      先計算11 1111 1111 1111 1111 1111 1111 011000的反碼:即補碼減1
      1111 1111 1111 1111 1111 1111 1101 1000 -1=1111 1111 1111 1111 1111 1111 1101 0100
      再求1111 1111 1111 1111 1111 1111 1101 0100原碼:最高位不變,其他位取反
      1111 1111 1111 1111 1111 1111 1101 0100轉換原碼結果是1000 0000 0000 0000 0000 0000 0010 1000
      1000 0000 0000 0000 0000 0000 0010 1000 轉換為十進位制輸出就是-40

       */
      System.out.println("有符號十進位制負整數-10左移2位的結果是" + (-10 << 2));
  }
}

程式執行結果

  • 有符號右移(>>):二進位制位向右移動,使用符號位進行補位,符號位是1那就補上1,符號位是0就補0。有符號右移規律:向右移動幾位,就是除以2的幾次方

有符號整數的右移案例 32 >> 2,-32 >> 2

32 >> 2的計算過程

-32 >> 2的計算過程

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符號整數的右移案例 32 >> 2,-32 >> 2
 * 正整數和負整數的右移運算
 *  有符號右移(>>):二進位制位向右移動,使用符號位進行補位,符號位是1那就補上1,符號位是0就補0。有符號右移規律:向右移動幾位,就是除以2的幾次方
 * @author tony [email protected]
 * @version 2020/12/12 12:02
 * @since JDK11
 */
public class BitRightMoveSignedInt {

  public static void main(String[] args) {

    /*
        32 >> 2 的計算過程
        整數常量預設是4個位元組
        32是四個位元組的有符號十進位制整數
        其二進位制補碼錶示方式為0000 0000 0000 0000 0000 0000 0010 0000

        0000 0000 0000 0000 0000 0000 0010 0000 向右移動2位,右邊去掉2個0,符號位是1,左邊補上2個0
        0000 0000 0000 0000 0000 0000 0000 1000
        1000 轉換為十進位制的結果是8

     */
    System.out.println("有符號十進位制正整數32右移2位的結果是"+(32 >> 2));

    /*
        -32 >> 2 的計算過程
        整數常量預設是4個位元組
        -32是四個位元組的有符號十進位制整數
        其二進位制原碼錶示方式為1000 0000 0000 0000 0000 0000 0010 0000
        其二進位制反碼錶示方式為1111 1111 1111 1111 1111 1111 1101 1111
        其二進位制補碼錶示方式為1111 1111 1111 1111 1111 1111 1110 0000

        1111 1111 1111 1111 1111 1111 1110 0000 向右移動2位,右邊去掉兩個0,左邊補上兩個1
        右移兩位的結果是1111 1111 1111 1111 1111 1111 1111 1000
        其二進位制反碼錶示方式為 1111 1111 1111 1111 1111 1111 1111 0111
        其二進位制原碼錶示方式為 1000 0000 0000 0000 0000 0000 0000 1000
        轉換為十進位制的結果是-8
     */
    System.out.println("有符號十進位制正整數32右移2位的結果是"+(32 >> 2));


  }
}

程式執行結果

  • 無符號右移(>>>):二進位制位向右移動,無論符號位是0還是1,左邊都補0。無符號右移,對於負數來說,右移後會變成正數。

有符號整數的無符號右移案例 188 <<< 2 和 -188 <<< 2

188 <<< 2的計算過程

-188 <<< 2的計算過程

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符號整數的無符號右移案例 188 <<< 2 和 -188 <<< 2
 * 無符號右移(>>>):二進位制位向右移動,無論符號位是0還是1,左邊都補0。無符號右移,對於負數來說,右移後會變成正數。
 * @author tony [email protected]
 * @version 2020/12/12 12:57
 * @since JDK11
 */
public class UnsignedBitRightMoveSignedInt {

  public static void main(String[] args) {

      /*
            188 <<< 2的計算過程
            188的二進位制原碼錶示方式為 0000 0000 0000 0000 0000 0000 1011 1100
          向右邊無符號移動2位的結果0000 0000 0000 0000 0000 0000 0010 1111
          10 1111轉換為十進位制47
       */

    System.out.println("有符號十進位制正整數188的無符號右移2位的結果是"+ (188 >>> 2) );

    /*
        -188 <<< 2的計算過程
            -188的二進位制原碼錶示方式為 1000 0000 0000 0000 0000 0000 1011 1100
            -188的二進位制反碼錶示方式為 1111 1111 1111 1111 1111 1111 0100 0011
            -188的二進位制補碼錶示方式為 1111 1111 1111 1111 1111 1111 0100 0100

          向右邊無符號移動2位,去掉右邊的兩個0,左邊補上兩個0
         001111 1111 1111 1111 1111 1111 0100 01 轉換為十進位制的結果是1073741777

     */
    System.out.println("有符號十進位制負整數-188無符號右移2位的結果是"+ (-188 >>> 2) );
  }
}

程式執行結果