JavaScript中,将十进制数字转换为二进制的方法是使用toString(2)
方法,对于正整数的返回值是从不为零的首位开始返回:
(8).toString(2); // 1000
对于负数,不会返回其真正的二进制编码(即其相反数的补码),而是直接利用符号-
来表示:
(-8).toString(2); // -1000
所以,想要求出负数的二进制编码,需要首先求出反码,转换为十进制数字,加一后求出反码在转换为二进制数字:
// 求负数的二进制
const getNegativeBinary = num => {
// 求相反数的二进制编码
const positiveBinary = (-num).toString(2).padStart(32, '0');
// 求反码
const reverseBinary = positiveBinary.split('').map(v => +v === 1 ? 0 : 1).join('');
// 求反码的十进制数
const reverseDecimal = parseInt(reverseBinary, 2);
// 求补码
const complement = reverseDecimal + 1;
// 求补码的二进制
const str = complement.toString(2);
return handleString(str);
};
上面是针对于整数部分,针对于小数部分,JavaScript处理时采用的是“乘二取整, 顺序排列”法获得的,具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。
在JavaScript使用的IEEE754的双精度数值,一个JavaScript的number表示应该是二进制如下格式:
1[-/+] 11[位指数-移动位数] 52[数值] 64位长
- -------- -----------------------
可以看到,由于二进制的精确位数只有52+1位,那么类似1/3
这样的无理数,那么肯定是无法表示的,而且二进制还有很多有理数0.1
这样的也无法在52位精度的范围内表示精确无误;都会被截取53位以后的所有数字。
JavaScript采用了17位来默认截取数据,根据四舍五入方法或者是说二进制中的0舎1进位的方式截取。所以这样的加法有的时候会出现精度问题,有的又不会。例如0.1 + 0.2 !== 0.3
,而0.1 + 0.3 === 0.4
小数使用toString(2)
方法时,会分别返回整数和小数的二进制表示:
(8.1).toString(2);
// "1000.0001100110011001100110011001100110011001100110011"
(-8.1).toString(2);
// "-1000.0001100110011001100110011001100110011001100110011"
所有按位操作符的操作数都会转换完成二进制的形式进行操作。左起第一位是符号位,0
代表正数,1
代表负数。
314
二进制编码为:
0000000 00000000 00000001 00111010
-314
的二进制编码,首先求314
的反码:
11111111 11111111 11111110 11000101
然后求314
的补码(反码加一):
11111111 11111111 11111110 11000110
0
的二进制编码都是0:
00000000 00000000 00000000 00000000
-1
的二进制编码都是1:
11111111 11111111 11111111 11111111
数字-2147483648
和2147483647
是32位有符号数字所能表示的最小和最大整数。
// -2147483648
10000000 00000000 00000000 00000000
// 2147483647
011111111 11111111 11111111 11111111
JavaScript中的Number
类型实际上一个基于IEEE754标准的双精度64位浮点数,进行按位操作时会将操作数转换为32位比特序列进行处理,返回结果仍然是JavaScript标准数值(按照64位浮点数存储)。
JavaScript中的按位操作符有以下几种:
运算符 | 用法 | 描述 |
---|---|---|
按位与 | a & b |
对于一个二进制数字,二者都为1时,结果为1,否则为0 |
按位或 | a | b |
对于一个二进制数字,至少有一个为1时,结果为1,否则为0 |
按位异或 | a ^ b |
对于一个二进制数字,有且只有一个为1时,结果为1,否则为0 |
按位非 | ~ a |
翻转二进制数字,即0变成1,1变成0 |
左移 | a << b |
将a 的二进制形式向左移动b (<32 )位,右边用0填充 |
有符号右移 | a >> b |
将a 的二进制表示向右移动b (<32 )位,丢弃被移出的位 |
无符号右移 | a >>> b |
将a 的二进制表示向右移动b (<32 )位,丢弃被移出的位,左边用0填充 |
按位逻辑操作符遵循下面的规则:
(1)操作数被转换为32为二进制数,超过32位的数字会被丢弃(会导致精度丢失):
转换前: 111001101111 10100000 00000000 01100000 00000001
转换后: 10100000 00000000 01100000 00000001
(2)第一个操作数与第二个操作数的每个二进制位一一对应
(3)返回值是一个新的以10进制形式表示数字
&
a & b
,a
和b
均是二进制取值,只有a
和b
都为1时,结果才为1,否则为0
9 00000000 00000000 00000000 00001001
14 00000000 00000000 00000000 00001110
9 & 14 00000000 00000000 00000000 00001000 8
任一数值与0
进行按位与操作,结果都是0
,任一数值x
与-1
进行按位与操作,结果都是x
|
a | b
,a
和b
均是二进制取值,只有a
和b
有一个为1时,结果就为1,否则为0
9 00000000 00000000 00000000 00001001
14 00000000 00000000 00000000 00001110
9 | 14 00000000 00000000 00000000 00001111 15
任一数值x
与0
进行按位或操作,结果都是x
,任一数值与-1
进行按位或操作,结果都是-1
^
a ^ b
,a
和b
均是二进制取值,a
和b
取值不同时,结果为1,否则为0
9 00000000 00000000 00000000 00001001
14 00000000 00000000 00000000 00001110
9 ^ 14 00000000 00000000 00000000 00000111 7
任一数值x
与0
进行按位异或操作,结果都是x
,任一数值与-1
进行按位异或操作,结果都是~x
异或还有一个特性,一个数,异或同一个数两次,原数不变:
let a = 5;
console.log(a ^ 12 ^ 12); // 5
~
~a
,对a
求反码
9 00000000 00000000 00000000 00001001
~9 11111111 11111111 11111111 11110110 -10
对任一数值x
进行按位非操作,结果是-(x+1)
可以用~str.indexOf(a)
来代替str.indexOf(a) !== -1
表示字符串中包含a
对于非数值类型(默认转换后)进行取反操作时,相当于对0进行取反操作,结果都是-1:
~true; // -2
~'1'; // -2
~'1d'; // -2
~'d1'; // -1
~'NaN'; // -1
~'true'; // -1
~Infinity; // -1
<<
按位移动操作符有两个操作数,第一个是要被移动的数字,第二个是要移动的长度,按位移动会将操作数转换为大端字节序(big-endian order)的32为整数
左移a << b
,将a
向左移动b
位,移出的数字被丢弃,右侧用0
补充,例如,9 << 2
:
9 00000000 00000000 00000000 00001001
9 << 2 00000000 00000000 00000000 00100100 36
进行左移操作a << b
,结果是a * 2y
>>
a >> b
会将a
向右移动b
位,移出的数字被丢弃,拷贝最左侧的位以填充左侧,符号不会被改变,例如:
9 00000000 00000000 00000000 00001001
9 >> 2 00000000 00000000 00000000 00000010 2
-9 11111111 11111111 11111111 11110111
-9 >> 2 11111111 11111111 11111111 11111101 -3
>>>
a >>> b
会将a
向右移动b
位,移出的数字被丢弃,左侧用0填充,符号可能被改变,例如:
9 00000000 00000000 00000000 00001001
9 >>> 2 00000000 00000000 00000000 00000010 2
-9 11111111 11111111 11111111 11110111
-9 >>> 2 00111111 11111111 11111111 11111101 1073741821
-9 >>> 0 01111111 11111111 11111111 11110111 4294967205
对于整数,有符号右移>>
和无符号右移>>>
相同,对于负数不同,无符号右移永远会返回正数
>>>0
时实际上并没有发生数位变化,但是却会把符号位替换成0
前面提到了,JavaScript进行按位操作时会将操作数转换为32位比特序列进行处理,返回结果按照64位浮点数存储
在进行位运算时,小数部分会被直接抛弃,所以可以通过双取反操作来取整:
~~2.5 // 2
~~5.44 // 5
~~-1.2 // -1
parseInt
对科学技术法表示的小数取整是有问题的::
parseInt(0.01); // 0
parseInt(1e-6); // 1
parseInt(0.0000001); // 1,相当于parseInt(1e-6)
导致取整的过程中会出现问题。
前面提到了,因为JavaScript对小数的位运算的处理是直接抛弃,所以可以通过双取反操作来对数字取整
要注意的是,对非数字类型的双取反操作都返回0:
~~5.2; // 5
~~-5.2; // -5
~~'5'; // 5
~~'5.2'; // 5
~~'5.2f'; // 0
~~'NaN'; // 0
~~NaN; // 0
也可以通过有符号右移来取整:
5.2 >> 0; // 5
-5.2 >> 0; // -5
'5' >> 0; // 5
'5.2' >> 0; // 5
'5.2f' >> 0; // 0
'NaN' >> 0; // 0
NaN >> 0; // 0
也可以通过与0按位或或者与-1按位与来实现
indexOf
表达式可以用取反操作(按位非)来简化indexOf
表达式:
if (~str.indexOf('a')) {
// 包含字符串
}
在ES6中可以使用includes
方法
前面说过求负数的二进制补码的过程,可以使用无符号右移来获取负数的二进制编码:
// -314 11111111 11111111 11111110 11000110
(-314 >>> 0).toString(2); // 11111111 11111111 11111110 11000110
之所以能够实现,是因为JS的按位操作符是有符号的32位二进制数,超出了表达范围的值,符号会发生改变,例如-1
对应的二进制数,经由未操作符转换后在转换为十进制,是一个超出范围的正值4294967295
可以使用按位异或操作符实现1和0的翻转:
[1, 0, 1, 0].map(v => v ^ 1);
// [0, 1, 0, 1]
let arr = [1, 2, 3, 2, 1];
let reulst = arr[0]
for(let i = 1; i < arr.length; i++) {
result = result ^ arr[i]
}
利用的原理是,一个数,异或两个相同的数,原数不变。