原码, 反码, 补码
原码(True Form)
使用高位作为符号位。最高位为0时表示正数,最高位为1时则表示为负数。其余位使用此数字数值本身二进制的绝对值表示。
原码的设计不便于加减运算。
反码(Ones’ Complement)
使用高位作为符号位。最高位为0时表示正数,最高位为1时则表示为负数。正数的反码还是正数本身,负数的补码就是其绝对值相同的正数取反的结果。即在原码的基础上,符号位不变,其他位取反的结果。
反码的设计依然不便于加减运算。
补码(Twos’ Complement)
使用高位作为符号位。最高位为0时表示正数,最高位为1时则表示为负数。正数的补码就是正数本身,负数的补码就是其原码取反加一的结果。补码设计的目的,就是讲将原二进制数,分一半做负数,而取反加一的规律则是在此设计的基础上归纳得来的。所以,使用“取反加一”来定义补码,是与设计相悖的。
补码的设计,方便了二进制的加减运算。
转换
- 正数:原码,反码,补码都一样。
- 负数:
- 原码和反码的相互转换:符号位不变,数值位按位取反
- 原码和补码的相互转换:符号位不变,数值位按位取反,末位再加1
- 已知补码,求原码的负数的补码:符号位和数值位都取反,末位再加1
1 | // 8的八位原码,反码,补码 |
位操作符
概述
按位操作符(Bitwise operators) 将其操作数(operands)当作32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值。例如,十进制数9,用二进制表示则为1001。按位操作符操作数字的二进制形式,但是返回值依然是标准的JavaScript数值。
运算符 | 用法 | 描述 |
---|---|---|
按位与( 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 在左侧填充。 |
有符号32位整数
所有的按位操作符的操作数都会被转成补码(two’s complement)形式的有符号32位整数。补码形式是指一个数的负对应值(negative counterpart)(如 5和-5)为数值的所有比特位反转后,再加1。反转比特位即该数值进行’非‘位运算,也即该数值的反码。
按位逻辑操作符
从概念上讲,按位逻辑操作符按遵守下面规则:
- 操作数被转换成32位整数,用比特序列(0和1组成)表示。超过32位的数字会被丢弃。
例如, 以下具有32位以上的整数将转换为32位整数。 - 第一个操作数的每个比特位与第二个操作数的相应比特位匹配:第一位对应第一位,第二位对应第二位,以此类推。
- 位运算符应用到每对比特位,结果是新的比特值。
1 | function logOperate(a, b, op) { |
& (按位与)
对每对比特位执行与(AND)操作。只有 a 和 b 都是 1 时,a AND b 才是 1。与操作的真值表如下:
a | b | a&b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
将任一数值 x 与 0 执行按位与操作,其结果都为 0。将任一数值 x 与 -1 执行按位与操作,其结果都为 x。
1 | logOperate(2, 3, '&'); |
| (按位或)
对每一对比特位执行或(OR)操作。如果 a 或 b 为 1,则 a OR b 结果为 1。或操作的真值表:
a | b | a | b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
将任一数值 x 与 0 进行按位或操作,其结果都是 x。将任一数值 x 与 -1 进行按位或操作,其结果都为 -1。
1 | logOperate(2, 3, '|'); |
1 | 1 | 0 ; // 1 |
^ (按位异或)
对每一对比特位执行异或(XOR)操作。当 a 和 b 不相同时,a XOR b 的结果为 1。异或操作真值表:
a | b | a ^ b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
1 | logOperate(2, 3, '^'); |
将任一数值 x 与 0 进行异或操作,其结果为 x。将任一数值 x 与 -1 进行异或操作,其结果为 ~x。
~ (按位非)
对每一个比特位执行非(NOT)操作。NOT a 结果为 a 的反转(即反码)。非操作的真值表:
a | ~a |
---|---|
0 | 1 |
1 | 1 |
对任一数值 x 进行按位非操作的结果为 -(x + 1)。例如,~5 结果为 -6。
1 | // 判断字符串是否存在: |
按位移动操作符
按位移动操作符有两个操作数:第一个是要被移动的数字,而第二个是要移动的长度。移动的方向根据操作符的不同而不同。
按位移动会先将操作数转换为大端字节序顺序(big-endian order)的32位整数,并返回与左操作数相同类型的结果。右操作数应小于 32位,否则只有最低 5 个字节会被使用。
1 | 注:Big-Endian:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端, |
<< (左移)
该操作符会将第一个操作数向左移动指定的位数。向左被移出的位被丢弃,右侧用 0 补充。
在数字 x 上左移 y 比特得到 x * 2y.
1 | logOperate(2, 3, '~'); // 16 |
>> (有符号右移)
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。
1 | logOperate(8, 3, '>>'); |
>>> (无符号右移)
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)
对于非负数,有符号右移和无符号右移总是返回相同的结果。
1 | logOperate(8, 3, '>>>'); |
掩码
位运算经常被用来创建、处理以及读取标志位序列——一种类似二进制的变量。虽然可以使用变量代替标志位序列,但是这样可以节省内存(1/32)。
掩码 (bitmask) 是一个通过与/或来读取标志位的位序列。典型的定义每个标志位的原语掩码如下,
例如,有四个标志位:A,B,C,D
1 | var FLAG_A = 1; // 0001 |
新的掩码可以在以上掩码上使用逻辑运算创建。例如,掩码 1011 可以通过 FLAG_A、FLAG_B 和 FLAG_D 逻辑或得到:
1 | var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011 |
某个特定的位可以通过与掩码做逻辑与运算得到,通过与掩码的与运算可以去掉无关的位,得到特定的位。例如,掩码 0100 可以用来检查标志位 C 是否被置位:
1 | // 判断是否含有FLAG_C |
一个有多个位被置位的掩码表达任一/或者的含义。例如,以下两个表达是等价的:
1 | // 如果我们有 FLAG_B 或者 FLAG_C 至少一个 |
可以通过与掩码做或运算设置标志位,掩码中为 1 的位可以设置对应的位。例如掩码 1100 可用来设置位 C 和 D:
1 | var mask = FLAG_C | FLAG_D; // 0100 | 1000 => 1100 |
可以通过与掩码做与运算清除标志位,掩码中为 0 的位可以设置对应的位。掩码可以通过对原语掩码做非运算得到。例如,掩码 1010 可以用来清除标志位 A 和 C :
1 | var mask = ~(FLAG_A | FLAG_C); // ~0101 => 1010 |
如上的掩码同样可以通过 ~FLAG_A & ~FLAG_C 得到(德摩根定律):
1 | var mask = ~FLAG_A & ~FLAG_C; |
标志位可以使用异或运算切换。所有值为 1 的为可以切换对应的位。例如,掩码 0110 可以用来切换标志位 B 和 C:
1 | var mask = FLAG_B | FLAG_C; |
最后,所有标志位可以通过非运算翻转:
1 | // entering parallel universe... |
位操作使用实例
1.判断一个数a的奇偶:
1 | // 奇数 |
2.两个整数x,y的平均值:
1 | const avarage = (x, y) => (x & y) + ((x ^ y) >> 1); |
3.不使用缓存交换两个整数:
1 | // 正常版本 |
4.老鼠试毒:
假设有1000杯水,其中有一杯有毒,至少用多少只老鼠试出哪杯有毒?
1 | // 使用位掩码解决 |
5.权限问题:
在实际开发中,我们常常遇到权限的判断的问题,比如说,不同的用户对系统有不同的操作权限,有的用户可能有多种权限,我们最常规的办法就是每一个权限定义一个BOOL值。
假设,某系统有4种权限,那么,就有了:
1 | // var role = { |