[JS] 有符号整数的位操作

JavaScript011

[JS] 有符号整数的位操作,第1张

按位操作符(Bitwise operators)会使用内置函数, 7.1.5 ToInt32 ( argument ) ,

先将其操作数转换成32位有符号整数 ,再进行位操作,最后返回一个32位有符号整数。

包括,

12.5.8 Bitwise NOT Operator ( ~ ) ,

12.9.3 The Left Shift Operator ( <<) ,

12.9.4 The Signed Right Shift Operator ( >>) ,

12.12 Binary Bitwise Operators

因此, a | 0 , 0 | a ,都可以将变量 a 中数值转换为32位有符号整数。

某些特殊的值,并不是32位有符号整数的安全范围,它们会被转换为 0 。

在计算机中表示有符号整数,通常使用 补码 (two's-complement)进行编码。

它将字的最高有效位解释为符号位,符号位被置为 1 时,表示值为负,

符号位被置为 0 时,表示值为非负。

因此,字长为4的二进制数 0001 表示整数 1 ,其中 0*2^3+0*2^2+0*2^1+1*2^0=1 ,

而 1111 就表示整数 -1 ,其中 -1*2^3+1*2^2+1*2^1+1*2^0=-1 。

负数的补码,还可以按照“ 逐位取反后,加一 ”的方式来获取相应的整数值。

例如, 1111 逐位取反 0000 ,然后再加一 0001 ,它是 1 的二进制表示,

因此 1111 就是表示 -1 了。

~ 操作符,它首先将操作数转换成32位有符号整数,然后再按位取反。

例如, 1 的32位补码编码为,

按位取反,

它表示什么呢?

先看最高为的符号位,是 1 ,它表示一个负数,

然后“逐位取反后,加一”, 00000000 00000000 00000000 00000002 值为 2 ,

因此, 11111111 11111111 11111111 11111110 表示 -2 。

一般的, 可以证明

对于任意的32位有符号整数 x 来说, ~x === -(x+1) 。

详细证明见文后的附录。

ECMAScript中,数组元素的索引范围是, 0 到 Math.pow(2,32)-2 。

规范 9.4.2 Array Exotic Objects 中指出,

超过数组 length 的索引,会被看做数组的属性值,

因此, indexOf 返回的最大值为 Math.pow(2,32)-2 。

Array.prototype.indexOf ,

会返回给定数组元素在数组中的索引,如果找不到给定元素,就返回 -1 。

因为只有 ~-1 等于 0 ,其他索引值取反都非 0 ,

所以,人们经常使用 !~a.indexOf(element) 来判断元素是否在数组中。

这里有一个值得注意的事情,由于 ~ 会首先将操作数转换成32位有符号整数,

所以, -1 和 Math.pow(2,32)-1 具有相同的编码,

但是,数组的最大索引为 Math.pow(2,32)-2 ,小于上面这个值,

因此,对 indexOf 返回的值进行取反,除了 -1 之外,总是非 0 值,是安全的做法。

下面给出 ~x === -(x+1) 的证明。

(1)先看正整数

对于32位正整数来说,它的二进制编码为,

其中 n 表示 0 或者 1 ,

则, ~x 为,

其中 u 为 n 的取反结果。

以上二进制表示,如果看成32位有符号整数,则由于符号位 1 ,它是一个负数,

其绝对值为,“逐位取反后,加一”,即为, (0nnnnnnn nnnnnnnn nnnnnnnn nnnnnnnn)+1 === x+1 ,

即, -(x+1) 。

因此,对于正数, ~x === -(x+1) 。

(2)再看负整数

对于32位负整数来说,它的二进制编码为,

其中 n 表示 0 或者 1 ,

则, ~x 为,

其中 u 为 n 的取反结果。

设 0uuuuuuu uuuuuuuu uuuuuuuu uuuuuuuu 的值为 t ,

则 1nnnnnnn nnnnnnnn nnnnnnnn nnnnnnnn 的值为, -(t+1) ,(逐位取反后,加一)。

因此, x === -(t+1) , ~x === t ,

即, ~x === t === -(-(t+1) + 1) === -(x+1) 。

证毕。

你不知道的JavaScript(中卷)

ECMAScript Language Specification

深入理解计算机系统

在了解位运算之前, 必须先了解一下什么是原码, 反码和补码, 以及二进制与十进制的转换.

原码

一个数在计算机中是以二进制的形式存在的, 其中第一位存放符号, 正数为0, 负数为1. 原码就是用第一位存放符号的二进制数值. 例如2的原码为00000010, -2的原码为10000010

反码

正数的反码是它本身, 负数的反码是在其原码的基础上, 符号位不变, 其余各位取反.

可见如果一个反码表示的是负数, 并不能直观的看出它的数值, 通常要将其转换成原码再计算

补码

正数的补码是它本身, 负数的补码是在其原码基础上, 符号位不变, 其余各位取反, 最后+1. (即负数的补码为在其反码的基础上+1)

可见对于负数, 补码的表示方式也是让人无法直观的看出其数值的, 通常也需要转换成原码再计算.

正整数十进制转二进制

正整数的十进制转二进制的方法为将一个十进制数除以2, 得到的商再除以2, 以此类推知道商为1或0时为止, 倒序取得除得的余数, 即为转换所得的二进制数.

负整数十进制转二进制

负整数的十进制转二进制, 先将该负整数对应的正整数转为二进制, 然后对其取反再+1. 即补码的形式

十进制小数转二进制

十进制小数转二进制的方法为"乘2取整", 对十进制的小数部分乘2, 得到的整数部分即是相应的二进制码数, 然后继续对得到的小数部分乘2, 如此不断重复, 直到小数部分为0或达到精度要求为止. 顺序取得每次的整数部分, 即是该十进制小数的二进制表示.

按位运算符有6个

&: 按位与

|: 按位或

^: 按位异或

~: 按位取反

>>: 右移

<<: 左移

将运算数以二进制表示, 对应位都为1, 则结果为1, 否则为0.

使用场景示例:

判断一个数是奇数还是偶数

奇数的二进制码的最后一位数肯定是1, 而1只有最后一位为1, 按位与运算后, 结果肯定只有最后一位数是1. 而偶数的二进制表示的最后一位数是0, 和1进行按位与运算, 结果的所有位都是0.

将运算数以二进制表示, 对应位有一个为1, 则结果为1, 否则为0.

使用场景示例:

对浮点数向下求整

其实浮点数是不支持位运算的, 所以会先把小数位丢弃, 然后以整数进行位运算, 而任何数与0进行按位或操作, 结果都是它本身, 就好像是对浮点数向下求整.

将运算数以二进制表示, 对应位相同为0, 相异为1.

异或满足交换律和结合律, 数字与它本身进行异或操作, 得到0数字与0进行异或操作, 得到它本身.

使用场景示例:

交换两个变量数字的值

将操作数转换为二进制数, 然后按位求反.

浮点数是不支持位运算的,所以会先直接去除小数部分,转成整数再进行位运算,就好像是对浮点数向下求整.

~~可以进行类型转换,位运算会默认将非数字类型转换成数字类型再进行运算 (转换结果为整数 直接去除小数部分)

使用场景示例:

类型转换

移位运算符将操作数转换成二进制, 然后向左或向右移动, 超过的位丢弃, 空出的位补0.

使用场景示例:

类型转换

任何小数 把它 >>0可以取整

如3.14159 >>0 = 3

其默认将非数字类型的转换为数字类型再做运算的性质与 ~~ , | 0 一样