整数的存储
在计算机中,所有的数据都是以二进制的形式储存的。在 c++ 中,整数储存的位数往往和编译器的实现有关,而在 ECMAScript 中的所有数值都是以 IEEE-754 64位的格式储存的,当然位操作符并不直接操作 64 位的值。而是先将 64 位转换为 32 位,然后执行操作,最后再将结果转换位 64 位。对于开发人员来说,整个过程就好像 64 位格式是透明的,只存在 32 位的整数一样。
学习过 c++ 语言基础的同学都知道,对于有符号的整数,32位中的前31位都用来表示数值,而第32位用来表示符号位:0 表示正数,1 表示负数,这个叫做符号位。对于正数,以纯二进制格式存储,而负数,则以二进制补码的格式。计算补码,则是将二进制的原码取反加 1。比如要求得 -18 的二进制表示:
# 我们首先求得 18 的二进制,即:
0000 0000 0000 0000 0000 0000 0001 0010
# 然后每一位取反,获得其二进制的反码:
1111 1111 1111 1111 1111 1111 1110 1101
# 然后再加 1,获得补码:
1111 1111 1111 1111 1111 1111 1110 1110
当然,最左边的第32位,在处理有符号整数时是不能访问的。
按位非(NOT)
按位非操作符在 JS 中由一个波浪线 ~
表示,返回的结果就是每一位取反获得的反码。根据负数的规则,我们可以得出反码的本质:操作数的负值减 1。但由于位操作符在底层直接操作,因此速度更快。
var a = 25; // 0000 0000 0000 0000 0000 0000 0001 1001
var b = ~25; // 1111 1111 1111 1111 1111 1111 1110 0110
console.log(b) // -26
按位与(AND)
按位与由一个 &
符号表示,它有两个操作符数,本质上就是每一位对齐然后对相同位置上的值进行 AND 操作。所谓 AND 操作,和逻辑上的 AND 类似:
第一数值 | 第二个数值 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
举个例子:
var result = 25 & 3
console.log(result) // 1
//过程如下:
// 25 = 0000 0000 0000 0000 0000 0000 0001 1001
// 3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
// AND = 0000 0000 0000 0000 0000 0000 0000 0001
// 即结果为 1
按位或(OR)
按位或有 |
符号表示,同样也是两个操作数,对每一位进行类似逻辑上的或操作:
第一数值 | 第二个数值 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
var result = 25 | 3
console.log(result) // 27
//过程如下:
// 25 = 0000 0000 0000 0000 0000 0000 0001 1001
// 3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
// OR = 0000 0000 0000 0000 0000 0000 0001 1011
// 即结果为 27
按位异或(XOR)
按位异或(XOR)由 ^
表示,也有两个操作数,按下面的表来操作:
第一数值 | 第二个数值 | 结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
按位异或和按位或不同的地方在于,这个操作要求两个数值的对应位上必须相异时才返回 1,如果都是 0 或者都是 1 则返回 0。
var result = 25 ^ 3
console.log(result) // 26
//过程如下:
// 25 = 0000 0000 0000 0000 0000 0000 0001 1001
// 3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
// XOR = 0000 0000 0000 0000 0000 0000 0001 1010
// 即结果为 26
左移
左移由 <<
表示,这个操作将数值的所有位向左移动指定的位数,左移并不会影响符号位。
var result = 2 << 5
console.log(result) // 64
//过程如下:
// 2 = 0000 0000 0000 0000 0000 0000 0000 0010
// ---------------------------------------------
// <<5 = 0000 0000 0000 0000 0000 0000 0100 0000
// |____|
// 以0填充
// 即结果为 64
有符号的右移
有符号的右移由操作符 >>
表示,有符号的右移与左移正好相反,即如果 64 右移 5位,结果为 2。同样,在操作时也会出现空位,只不过空位出现在原数值的左侧、符号位的右侧,并且会用符号位的值来填充所有的空位。
var result = 64 >> 5
console.log(result) // 2
//过程如下:
// 64 = 0000 0000 0000 0000 0000 0000 0100 0000
// ---------------------------------------------
// >>5 = 0000 0000 0000 0000 0000 0000 0000 0010
// |____|
// 以0填充
// 即结果为 2
无符号的右移
无符号的右移由操作符 >>>
表示,这个操作会将所有的 32 位都往右移动,对正数来说,结果仍然和有符号的右移相同。但对负数来说,结果就不一样了,首先,无符号右移是以 0 来填充空位,而不像有符号的一样以符号位的值来填充,其次,无符号右移会把负数的二进制码当作正数的二进制码,而且,由于负数以其绝对值的补码表示,因此右移的结果会非常之大。
var result = -64 >>> 5
console.log(result) // 134217726
//过程如下:
// 64 = 0000 0000 0000 0000 0000 0000 0100 0000
// -64 = 1111 1111 1111 1111 1111 1111 1100 0000
// ---------------------------------------------
// >>>5 = 0000 0111 1111 1111 1111 1111 1111 1110
// |____|
// 以0填充
// 即结果为 134217726