当前位置:   article > 正文

一文搞懂位运算_位运算示意

位运算示意

一,原码、反码、补码

 接下来我们主要介绍十进制数用二进制表示的不同方法,所以为了简洁,我们用一个字节,也就是8个bit来表示二进制数。

1,原码

十进制

原码

2

0000 0010

-2

1000 0010

原码其实是最容易理解的,只不过需要利用二进制中的第一位来表示符号位,0表示正数,1表示负数,所以可以看到,一个数字用二进制原码表示的话,取值范围是-111 1111 ~ +111 1111,换成十进制就是-127 ~ 127。

2,反码

在数学中我们有加减乘除,而对于计算机来说最好只有加法,这样计算机会更加简单高效,我们知道在数学中5-3=2,其实可以转换成5+(-3)=2,这就表示减法可以用加法表示,而乘法是加法的累积,除法是减法的累积,所以在计算机中只要有加法就够了。
一个数字用原码表示是容易理解的,但是需要单独的一个bit来表示符号位。并且在进行加法时,计算机需要先识别某个二进制原码是正数还是负数,识别出来之后再进行相应的运算。这样效率不高,能不能让计算机在进行运算时不用去管符号位,也就是说让符号位也参与运算,这就要用到反码

十进制

原码

反码

2

0000 0010

0000 0010

-2

1000 0010

1111 1101

正数的反码和原码一样,负数的反码就是在原码的基础上符号位保持不变,其他位取反。
那么我们来看一下,用反码直接运算会是什么情况,我们以5-3举例。
5 - 3 等于 5 + (-3)

十进制

原码

反码

5

0000 0101

0000 0101

-3

1000 0011

1111 1100

  1. 5-3
  2. = 5+(-3)
  3. = 0000 0101(反码) + 1111 1100(反码)
  4. = 0000 0001(反码)
  5. = 0000 0001(原码)
  6. = 1
  7. 0000 0101
  8. +1111 1100
  9. -----------
  10. 0000 0001
  11. 计算完成之后转换回原码
  12. 正数的反码和原码一样
  13. 所以最后结果为 0000 0001(原码)
  14. 转换成10进制为1

这不对呀? 5-3=1?,为什么差了1?

我们来看一个特殊的运算:

  1. 1-1
  2. = 1+(-1)
  3. = 0000 0001(反码) + 1111 1110(反码)
  4. = 1111 1111(反码)
  5. = 1000 0000(原码)
  6. = -0
  7. 0000 0101
  8. +1111 1110
  9. -----------
  10. 1111 1111
  11. 计算完成之后转换回原码
  12. 负数的原码符号位不变,其他位取反
  13. 所以最后结果为 1000 0000(原码)
  14. 转换为10进制为-0

我们来看一个特殊的运算:

  1. 0+0
  2. = 0000 0000(反码) + 0000 0000(反码)
  3. = 0000 0000(反码)
  4. = 0000 0000(原码)
  5. = 0
  6. 0000 0000
  7. +0000 0000
  8. -----------
  9. 0000 0000
  10. 计算完成之后转换回原码
  11. 正数的反码和原码一样
  12. 所以最后结果为 0000 0000(原码)
  13. 转换为10进制为0

我们可以看到1000 0000表示-0,0000 0000表示0,虽然-0和0是一样的,但是在用原码和反码表示时是不同的,我们可以理解为在用一个字节表示数字取值范围时,这些数字中多了一个-0,所以导致我们在用反码直接运算时符号位可以直接参加运算,但是结果会不对。

3,补码

为了解决反码的问题就出现了补码。

十进制

原码

反码

补码

2

0000 0010

0000 0010

0000 0010

-2

1000 0010

1111 1101

1111 1110

正数的补码和原码、反码一样,负数的补码就是反码+1。

十进制

原码

反码

补码

5

0000 0101

0000 0101

0000 0101

-3

1000 0011

1111 1100

1111 1101

  1. 5-3
  2. = 5+(-3)
  3. = 0000 0101(补码) + 1111 1101(补码)
  4. = 0000 0010(补码)
  5. = 0000 0010(反码)
  6. = 0000 0010(原码)
  7. = 2

5-3=2!!正确。
再来看特殊的:

  1. 1-1
  2. = 1+(-1)
  3. = 0000 0001(补码) + 1111 1111(补码)
  4. = 0000 0000(补码)
  5. = 0000 0000(反码)
  6. = 0000 0000(原码)
  7. = 0

1-1=0!!正确
再来看一个特殊的运算:

  1. 0+0
  2. = 0000 0000(补码) + 0000 0000(补码)
  3. = 0000 0000(补码)
  4. = 0000 0000(反码)
  5. = 0000 0000(原码)
  6. = 0

0+0=0!!也正确。
所以,我们可以看到补码解决了反码的问题。
所以对于数字,我们可以使用补码的形式来进行二进制表示。

二,Java中有三个位移运算

  • <<:左移
  • >>:右移
  • >>>:无符号右移
  1. System.out.println(2 << 1); // 4
  2. System.out.println(2 >> 1); // 1
  3. System.out.println(2 >>> 1); // 1
  4. System.out.println(-2 << 1); // -4
  5. System.out.println(-2 >> 1); // -1
  6. System.out.println(-2 >>> 1); // 2147483647

“2”和“-2”,这是两个十进制数,并且是int类型的(java中占四个字节),位运算是基于二进制bit来的,所以我们需要将十进制转换为二进制之后再进行运算。

2 << 1:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制左移一位,高位丢弃,低位补0,所以结果为“00000000 00000000 00000000 00000100”,换算成十进制则为“4”

2 >> 1:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制右移一位,低位丢弃,高位补0,所以结果为“00000000 00000000 00000000 00000001”,换算成十进制则为“1”

对于这两种情况非常好理解,那什么是无符号右移,以及负数是怎么运算的呢? 我们先来看-2 << 1与-2 >> 1,这两个负数的左移与右移操作其实和正数类似,都是先将十进制数转换成二进制数,再将二进制数进行移动,所以现在的关键是负数如何用二进制数进行表示。

负数位移运算,上面我们说了原码,反码,补码,知道负数运算都是先转换为补码运算完成后再转换为原码

我们再来看-2 << 1与-2 >> 1。

十进制原码反码补码
-210000000 00000000 00000000 0000001011111111 11111111 11111111 1111110111111111 11111111 11111111 11111110

-2 << 1,表示-2的补码左移一位后为11111111 11111111 11111111 11111100,该补码对应的反码为

  1. 11111111 11111111 11111111 11111100
  2. - 00000000 00000000 00000000 00000001
  3. --------------------------------------
  4. 11111111 11111111 11111111 11111011

该反码对应的原码为:符号位不变,其他位取反,为10000000 00000000 00000000 00000100,表示-4。
所以-2 << 1 = -4。

同理-2 >> 1是一样的计算方法,这里就不演示了。

无符号右移

上面在进行左移和右移时,我有一点没讲到,就是在对补码进行移动时,符号位是固定不动的,而无符号右移是指在进行移动时,符号位也会跟着一起移动。

比如-2 >>> 1

-2的补码右移1位为:01111111 11111111 11111111 11111111

右移后的补码对应的反码、原码为:01111111 11111111 11111111 11111111 (因为现在的符号位为0,表示正数,正数的原、反、补码都相同)
所以,对应的十进制为2147483647。
也就是-2 >>> 1 = 2147483647

三,位运算符与,或,非,异或

1,与(&)运算符

按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。只有对应的 两个二进位均为1时,结果位才为1 ,否则为0

例如 15&127

15 的二进制位: 00000000 00000000 00000000 00001111
127 的二进制为   00000000 00000000 00000000 01111111

15 & 127 :00000000 00000000 00000000 00001111 = 15

2,或(|)运算符

按位或运算符“|”是双目运算符。 其功能是参与运算的两数各对应的二进位相或。只要对应的 两个二进位有一个为1时,结果位就为1,否则为0

例如  127|128

127 的二进制位:00000000 00000000 00000000 01111111
128 的二进制位:00000000 00000000 00000000 10000000

127|128 :00000000 00000000 00000000 11111111 = 255

3,非(~)运算符

按位非运算符“~”是双目运算符。其功能是参非运算的数,对应位为0,结果位为1;对应位为1,结果位为0

例如  ~1
十进制 1 的二进制表示为:00000000 00000000 00000000 00000001

每位都取反为:01111111 11111111 11111111 11111110

4,异或(^)运算符

按位异或运算符“^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1,相同则为0

例如  2^3

2 的二进制位:00000000 00000000 00000000 00000010
3 的二进制位:00000000 00000000 00000000 00000011

2^3 :00000000 00000000 00000000 00000001

四,常用表达式

1,案例1

2 << 1 = 4 = 2*2
2 << 2 = 8 = 2*2*2
2 << n = 2*2n
m << n = m * 2n

右移则相反。

2,案例2

  1. package com.example.demo.bit;
  2. public class TestIntBit {
  3. public static void main(String[] args) {
  4. //0 -> 00000000 00000000 00000000 00000000 -> 0-31
  5. //1 -> 00000000 00000000 00000000 00000000 -> 32-63
  6. //2 -> 00000000 00000000 00000000 00000000 -> 64-95
  7. //3 -> 00000000 00000000 00000000 00000000 -> 96-127
  8. int[] ints = new int[4];
  9. //将100位修改为1
  10. select(ints);
  11. read(ints);
  12. //将100位修改为0
  13. unSelect(ints);
  14. read(ints);
  15. }
  16. private static void read(int[] ints) {
  17. //读出i位的状态 0 | 1
  18. //将100位修改为0
  19. int i = 100;
  20. //3
  21. int x = i / 32;
  22. //00000000 00000000 00000000 00010000
  23. //00000000 00000000 00000000 00010000
  24. //00000000 00000000 00000000 00010000
  25. //00000000 00000000 00000000 00000000
  26. //00000000 00000000 00000000 00010000
  27. //00000000 00000000 00000000 00000000
  28. int i1 = ints[x] & (1 << (i % 32));
  29. int i2 = i1 == 0 ? 0 : 1;
  30. System.out.println(i2);
  31. }
  32. /**
  33. * 将100位修改为0
  34. */
  35. private static void unSelect(int[] ints) {
  36. //将100位修改为0
  37. int i = 100;
  38. //3
  39. int x = i / 32;
  40. //之前不论是什么全部变为0
  41. //00000000 00000000 00000000 00010000
  42. //11111111 11111111 11111111 11101111
  43. int i1 = ~(1 << (i % 32));
  44. //00000000 00000000 00000000 00010000
  45. //11111111 11111111 11111111 11101111
  46. //00000000 00000000 00000000 00000000
  47. //ints[x] = i1 & ints[x];
  48. ints[x] &= i1;
  49. }
  50. /**
  51. * 将100位修改为1
  52. */
  53. private static void select(int[] ints) {
  54. //将100位修改为1
  55. int i = 100;
  56. //3
  57. int x = i / 32;
  58. //00000000 00000000 00000000 00010000
  59. int j = 1 << (i % 32);
  60. //00000000 00000000 00000000 00010000
  61. //00000000 00000000 00000000 00000000
  62. //00000000 00000000 00000000 00010000
  63. //ints[x] = ints[x] | j;
  64. ints[x] |= j;
  65. }
  66. }

执行结果

1
0

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/139627?site
推荐阅读
相关标签
  

闽ICP备14008679号