zoukankan      html  css  js  c++  java
  • 位运算简介

    位运算基础:

    运算符 用法 描述
    按位与( AND) a & b 对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0。
    按位或(OR) a | b 对于每一个比特位,当两个操作数相应的比特位至少有一个1时,结果为1,否则为0。
    按位异或(XOR) a ^ b 对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
    按位非(NOT) ~ a 反转操作数的比特位,即0变成1,1变成0。
    左移(Left shift) a << b a 的二进制形式向左移 b (< 32) 比特位,右边用0填充。
    有符号右移 a >> b 将 a 的二进制表示向右移b(< 32) 位,丢弃被移出的位。
    无符号右移 a >>> b 将 a 的二进制表示向右移b(< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

    需要注意以下几点:

    • 这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符;
    • 位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错;
    • 移位操作都是采取算术移位操作,算术移位是相对于逻辑移位,它们在左移操作中都一样,低位补0即可,但在右移中逻辑移位的高位补0而算术移位的高位是补符号位。如下面代码会输出-4和3;
    > -15>>2
    -4
    > 15>>2
    3
    

    因为15=0000 1111(二进制),右移二位,最高位由符号位填充将得到0000 0011即3。-15 = 1111 0001(二进制),右移二位,最高位由符号位填充将得到1111 1100即-4。

    • 位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成a = 1 << i + 1是不对的,程序会先执行i + 1,再执行左移操作。应该写成a = (1 << i) + 1;
    • 另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>=。

    原码、反码和补码

    1.1 原码

    一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为 0, 负数为 1。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

    原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如 8 位二进制:

    [+1] 原 = 0000 0001
    [-1] 原 = 1000 0001
    

    第一位是符号位,因为第一位是符号位,所以 8 位二进制数的取值范围就是:[1111 1111 , 0111 1111],即:[-127 , 127]

    1.2 反码

    反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。

    [+1] = [00000001] 原 = [00000001] 反
    [-1] = [10000001] 原 = [11111110] 反
    

    1.3 补码

    补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后 + 1。(即在反码的基础上 + 1)

    [+1] = [00000001] 原 = [00000001] 反 = [00000001] 补
    [-1] = [10000001] 原 = [11111110] 反 = [11111111] 补
    

    因为在电脑中存储数值都是用补码来进行存储的,所以对负数的计算首先要先算出它的补码值

    左移运算(<<)

    value << num
    

    num 指定要移位值;value 移动的位数。

    将左操作数(value)转为二进制数后向左边移动 num 位,并且在低位补 0,高位丢弃。

    例如:5 << 2

    0000 0000 0000 0000 0000 0000 0000 0101     5 的补码(同原码)
    0000 0000 0000 0000 0000 0000 0001 0100     左移 2 位后,低位补 0。换算成 10 进制为 20
    

    如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如:对 int 类型(最大位数 32)的数值移动 33 位,实际上只移动了 33 % 32 = 1 位。

    注:n 位二进制,最高位为符号位,因此表示的数值范围:−2(n−1)−2(n−1) —— 2(n−1)−12(n−1)−1,所以模为:2(n−1)2(n−1)。

    在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以 2 的 1 次方,左移 n 位就相当于乘以 2 的 n 次方。如:5 << 2 相当于 5∗22=205∗22=20。

    如果移进高阶位(int 31 或 long 63 位),那么该值将变为负值。如:1 << 31 = -2147483648

    右移运算(>>)

    value >> num
    

    num 指定要移位值;value 移动的位数。

    将左操作数(value)转为二进制数后向右边移动 num 位,符号位不变,高位补上符号位(若左操作数是正数,则高位补 0,若左操作数是负数,则高位补 1),低位丢弃。

    右移时,被移走的最高位(最左边的位)由原来最高位的数字补充,这叫做符号位扩展(保留符号位)(sign extension),在进行右移操作时用来保持负数的符号。

    例如:7 >> 2

    0000 0000 0000 0000 0000 0000 0000 0111     7 的补码(同原码)
    0000 0000 0000 0000 0000 0000 0000 0001     右移 2 位后,高位补 0。换算成 10 进制为 1
    

    例如:-7 >> 2

    1000 0000 0000 0000 0000 0000 0000 0111     -7 的原码
    1111 1111 1111 1111 1111 1111 1111 1000     -7 的反码
    1111 1111 1111 1111 1111 1111 1111 1001     -7 的补码
    1111 1111 1111 1111 1111 1111 1111 1110     右移 2 位后,高位补 1
    1000 0000 0000 0000 0000 0000 0000 0010     补码转原码。换算成 10 进制为 -2
    

    正数右移 n 位相当于除以 2 的 n 次方并且舍弃了余数。如:7 >> 2 相当于: 7/22=17/22=1。

    负数右移 n 位相当于除以 2 的 n 次方,如果有余数 -1。如:-7 >> 2 相当于: 7∗22−1=−27∗22−1=−2。

    无符号右移(>>>)

    value >>> num
    

    num 指定要移位值;value 移动的位数。

    将左操作数(value)转为二进制数后向右边移动 num 位,0 补最高位(忽略了符号位扩展)。

    无符号右移运算只是对 32 位和 64 位的值有意义。

    例如:-7 >>> 2

    1000 0000 0000 0000 0000 0000 0000 0111     -7 的原码
    1111 1111 1111 1111 1111 1111 1111 1001     -7 的补码
    0011 1111 1111 1111 1111 1111 1111 1110     右移 2 位后,高位补 0。换算成 10 进制为 1073741822
    

    -1的右移:

    负数的存储以补码(符号位保持不变,其他位是存储数的绝对值按位取反加1)方式:故而-1在存储空间的存放为:
    其值为2^32-1=4294967255;

    11111111  11111111  11111111  11111111
    

    无符号右移(>>>):

    故而无符号右移10位:变成如下图所示:

    00000000  00111111  11111111  11111111
    

    其值:

    2^22-1=4194303
    或
    (1<<22)-1=4194303
    

    右移(>>):

    11111111  11111111  11111111  11111111
    

    若为负数,则在存储时首位表示符号位,其值为1,表示该值是负数的移位,在移位过程中,高位补1,

    然后求补码加1;

    10000000  00000000  00000000  00000001
    
    -1>>10 结果还是-1
    

    若符号位是0,表示是正数,在移位过程中高位补零,两者的前提是符号位保持不变:

    其它:

    • Java 中整数类型(byte、short、int 和 long)在内存中是以有符号的二进制补码表示。所以位运算时,首先要转换为原码。
    • 补码转原码:补码转原码和原码转补码的方法是一样的,取反 + 1(补码的补码是原码)。
    • 当位运算数是 byte 和 short 类型时,将自动把这些类型扩大为 int 型(32 位)。
    • 计算出 n 位二进制数所能表示的最大十进制数位移算法:-1L ^ (-1L << n)~(-1L << 5)
    • byte 和 int 相互转换
    int i = 234;
    
    byte b = (byte) i; // 结果:b = -22
    // 转换过程:
    // 0000 0000 0000 0000 0000 0000 1110 1010      # int 234 的补码(与原码相等)
    //                               1110 1010      # byte 低位截取
    //                               1001 0110      # 求得补码,转为 10 进制为 -22
    
    int x = b ; // 结果为:x = -22;8 位 byte 的转 32 的 int,值不变。
    int y = b & 0xff; // 结果为:x = 234; 可以通过将其和 0xff 进行位与(&)得到它的无符值
    // 转换过程:
    // 1001 0110                                    # byte -22 的原码
    // 1000 0000 0000 0000 0000 0000 0001 0110      # int -22 的原码
    // 1111 1111 1111 1111 1111 1111 1110 1010      # int -22 补码
    // 0000 0000 0000 0000 0000 0000 1111 1111      # 0xff 的二进制数
    // 0000 0000 0000 0000 0000 0000 1110 1010      # 和 0xff 进与操作的结果,转换为 10 进制为 234
    

    参考:

    位运算简介及基本技巧

    有趣的二进制—高效位运算

    位运算——强大得令人害怕

    各种位运算

    按位操作符

    负数的带符号和不带符号的右移运算

    补码与位运算

    Java 位运算笔记

  • 相关阅读:
    3.5星|津巴多《时间的悖论》:未来导向的人更有可能取得个人的成功,但帮助他人的可能性更小
    成功的销售必须在失败时也能快乐:4星|《哈佛商业评论》2018年第4期
    被取代的金融工作岗位越来越多:3星|《被人工智能操控的金融业》
    比特币和区块链是泡沫,但是短期内不会破:4星|《财经》2018年第7期
    点赞是当代可卡因:3星|《欲罢不能:刷屏时代如何摆脱行为上瘾》
    如何使用GetManifestResourceStream
    隐藏光标与获得光标2----获得光标所在的控件
    端口是否使用
    是否联网以及热点问题
    注册表操作
  • 原文地址:https://www.cnblogs.com/hongdada/p/10040158.html
Copyright © 2011-2022 走看看