秒懂!进制和位运算

2,865 阅读9分钟

写在前面

计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。

关于进制

进制是计算机中数据的一种表示方法。 N进制的数可以用0~(N-1) 的数表示, 超过9的用字母A-F 表示 。

  • 十进制:用 0~9 的数表示 , 逢 10 进 1
  • 二进制:由 0-1组成
  • 八进制:由 0-7组成,逢 8 进 1
  • 十六进制: 由 0-9,A-F组成。0-9 对应 0-9,A-F对应10-15。字母不区分大小写

进制的转换

二进制转十进制

二进制数的第0位的权值是2的0次方,第一位是2的1次方......

所以,设有一个二进制数:101100100,转换为十进制为(从右向左):

0x2 0 + 0x2 1 + 1x2 2 + 0x2 3 + 0x2 4 + 1x2 5 + 1x2 6 + 0x2 7 + 1x2 8 = 356

八进制转十进制

八进制数的第0位的权值是8的0次方,第一位是8的1次方......

所以,设有一个八进制数:1507,转换为十进制为(从右向左):

7x8 0 + 0x8 1 + 5x8 2 + 1x8 3 = 839

十六进制转十进制

十六进制数的第0位的权值是16的0次方,第一位是16的1次方......

所以,设有一个十六进制数2AF5,转换为十进制为(从右向左):

5x16 0 + Fx16 1 + Ax16 2 + 2x16 3 = 10997

快速的进行2进制,10进制,16进制的相互转换

二进制的8421

首先我们来看一个二进制数:1111,它是多少呢? 你可能还要这样计算:1×2º+1×2¹+1×2²+1×2³=1×1+1×2+1×4+1×8=15。

我们必须直接记住1111每一位的权值,并且是从高位往低位记:8、4、2、1。

即,最高位的权值为2³=8,然后依次是 2² =4,2¹=2,2º=1。

记住8 4 2 1,对于任意一个4位的二进制数,我们都可以很快算出它对应的10进制值。

接下来我们练习 通过 8421 的方式 进行 快速的计算 , 2,10,16进制的转换

1111 = 8 + 4 + 2 + 1 = 15 =F
1110 = 8 + 4 + 2 + 0 = 14= E
1101 = 8 + 4 + 0 + 1 = 13= D
1100 = 8 + 4 + 0 + 0 = 12 =C
1011 = 8 + 0 + 2 + 1 = 11= B
1010 = 8 + 0 + 2 + 0 = 10 =A
1001 = 8 + 0 + 0 + 1 =9 =9
……
0001 = 0 + 0 + 0 + 1 = 1= 1
0000 = 0 + 0 + 0 + 0 = 0= 0
  • 二进制数转十六进制

    以4位一段,分别转换为十六进制,如:

  • 十六进制转二进制

    反过来,当我们看到 FD时,如何迅速将此十六进制转二进制呢?

    看到F,我们需知道它是15,然后15如何用8421凑呢?

    应该是:8 + 4 + 2 + 1,所以四位全为1 :1111。

    接着转换D,看到D,知道它是13,13如何用8421凑呢?

    应该是:8 + 4 + 1,即:1101。

    所以,FD转换为二进制数,为:1111 1101

  • 十进制转二进制

    由于十六进制转换成二进制相当直接,所以,我们需要将一个十进制数转换成二进制数时,也可以先转换成十六进制然后再转换成二进制。 比如,十进制数 1234转换成二制数,如果要一直除以2,直接得到2进制数,需要计算较多次数。所以我们可以先除以16,得到16进制数:

    1234÷16=77...2
    77÷16=4...13(D)
    4÷16=0...4
    

    十六进制为:4D2

    转二进制对应为:

    0100   4
    1101   D
    0010   2
    

    即十进制1234转二进制为:0100 1101 0010

  • 二进制转十进制

    同样,如果一个二进制数很长,我们需要将它转换成十进制数时,除了前面学过的方法是,我们还可以先将这个二进制转换成十六进制,然后再转换为10进制。如一个int类型的二进制数如下:

    0110 1101 1110 0101 1010 1111 0001 1011
    

    我们按四位一组转换为十六进制:6 D E 5 A F 1 B

    十六进制转化为十进制:

    Bx16 0 + 1x16 1 + Fx16 2 + Ax16 3 + 5x16 4 + Ex16 5 + Dx16 6 + 6x16 7 = 1843769115

  • 十进制转十六进制

    采余数定理分解,例如将487710转成十六进制:

    487710÷16=30481...14(E)
    30481÷16=1905...1
    1905÷16=119...1
    119÷16=7...716=0...7
    

    487710(10) = 7711E(16)

位运算

计算机中的计算都是以二进制来进行运算的,因此相比在代码中直接使用(+、-、*、/)运算符,合理使用位运算符能提高代码的执行效率。

符号描述运算规则
&位与两个位都为1时,结果才为1(1&1=1,0&0=0,1&0=0)
|位或两个位都为0时,结果才为0(1|1=1,0|0=0,1|0=1)
~位非(取反)1变0,0变1 (~1=0 ,~0=1)
位异或两个位相同为0,相异为1(1 ^ 1=0,1 ^ 0=1, 0 ^ 0=0)
>>有符号右移若正数,高位补0,负数,高位补1
<<有符号左移若正数,高位补0,负数,高位补1
>>>无符号右移不论正负,高位均补0

与(&)

4 & 6 = 4

首先我们需要把两个十进制的数转换成二进制

4  0 1 0 0
6  0 1 1 0
----------
4  0 1 0 0

用途

  • 清零:若想将一个单元清零,只要与一个各位都为0的数值相与,结果就为零

  • 判断奇偶:只要根据最末位判断是0还是1就可以判断,是0就是偶数,是1就是奇数

    if ((a & 1) == 0) //如果a是偶数
    代替
    if (a % 2 == 0) 
    

或(|)

4 & 6 = 6

首先我们需要把两个十进制的数转换成二进制

4  0 1 0 0
6  0 1 1 0
----------
6  0 1 1 0

用途

  • 常用来对一个数据的某些位设置为1:若要将一个数X=10101100的低四位设置为1,那么令Y=00001111,X与Y相或的结果就是将X的低四位设置为1(X | Y = 10101111)

非(~)

在Java中,所有数据的表示方法都是以补码的形式表示,如果没有特殊说明,Java中的数据类型默认是int,int数据类型是占4个字节,1字节为8位,所以int为32位,32bit。

补码与原码关系:正数补码与原码相同,负数补码是原码减1后取反

例如:5
原码是:00000000 00000000 00000000 00000101
补码是:00000000 00000000 00000000 00000101(计算机内存储)

例如:-5
原码:10000000 00000000 00000000 00000101
补码:11111111 11111111 11111111 11111011(计算机内存储)

注意:二进制中,最高位是符号位1表示负数,0表示正数

了解了原码补码我们来看位非~运算符

例:~4

4  0 1 0 0
----------
-5  1 0 1 1

4转为二进制是:0100

补码为:00000000 00000000 00000000 00000100

取反为:11111111 11111111 11111111 11111011(这就是~4在计算机存储的补码)

需要将补码计算出原码然后转化为十进制,高位不变,取反+1

10000000 00000000 00000000 00000101

转为十进制为:-5

所以 ~4 = -5

异或(^)

4 ^ 6 = 6

首先我们需要把两个十进制的数转换成二进制

4  0 1 0 0
6  0 1 1 0
----------
2  0 0 1 0

相同为0,不同为1

用途

  • 交换两个数:一个数和另一个数异或两次得到还是原来的数

    //不使用临时变量交换两个数
    void swap ( int a, int b ) {
        if ( a != b) {
            a = a ^ b;
            b = a ^ b;//ab异或两次了,现在值为a
            a = a ^ b;//用原来的ab表示的话,这句代码的意思是a^b^a
        }
    }
    

右移(>>)

4>>1 = 2

4转为二进制是:0100

正数,高位补0,负数,高位补1

左移(<<)

4<<1 = 8

4转为二进制是:0100

正数,高位补0,负数,高位补1

负数在非运算中已做说明,这里不在演示。

无符号右移(>>>)

无符号右移(>>>)只对32位和64位有意义。

在移动位的时候与右移运算符的移动方式一样的,区别只在于补位的时候不管是0还是1,都补0。

有趣的取模性质

取模a % (2^n) 等价于 a & (2^n - 1),所以在map里的数组个数一定是2的乘方数,计算key值在哪个元素中的时候,就用位运算来快速定位。

 public static void main(String[] args) {
        //取模a % (2^n) 等价于 a & (2^n - 1)
        System.out.println("the 345 % 16 is : " + (345 % 16)); //9
        System.out.println("the 345 % 16 is : " + (345 & (16 - 1)));//9
 }