参考链接:JavaScript 位运算符 (opens new window)
# 位运算符
| 运算符 | 名称 | 描述 |
|---|---|---|
| & | AND(按位与) | 如果对应位都是1则设置为1(有0得0,全1得1) |
| | | OR(按位或) | 如果对应位之一为1则设置为1(有1得1,全0得0) |
| ^ | XOR(按位异或) | 如果对应位只有一位为1则设置为1(相同得0,不同得1) |
| ~ | NOT(按位非) | 反转所有位 |
| << | 零填充左位移 | 通过从右推入零向左位移,并使最左边的位脱落 |
| >> | 有符号右位移 | 通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落 |
| >>> | 零填充右位移 | 通过从左推入零来向右位移,并使最右边的位脱落 |
- 按位与运算时,任何数字x与0都是0。
- 按位或运算时,任何数字x或1都是1。
- 按位非运算时,任何数字x的运算结果都是
-(x + 1)。 - 按位异或运算时,任何数字x异或自己结果都是0,异或0都是自己。
实例
| 操作 | 结果 | 等同于 | 结果 |
|---|---|---|---|
| 5 & 1 | 1 | 0101 & 0001 | 0001 |
| 5 | 1 | 5 | 0101 | 0001 | 0101 |
| 5 ^ 1 | 4 | 0101 ^ 0001 | 0100 |
| ~5 | 10 | ~0101 | 1010 |
| 5 << 1 | 10 | 0101 << 1 | 1010 |
| 5 >> 1 | 2 | 0101 >> 1 | 0010 |
| 5 >>> 1 | 2 | 0101 >>> 1 | 0010 |
TIPS
利用有符号右移运算符>>巧妙计算两个数的中间值。例如在二分法中:
let left = 0;
let right = len - 1;
const mid = Math.floor((left + right) / 2);
// 使用有符号右移
const mid = left + ((right - left) >> 1);
2
3
4
5
6
原理:
- 假设
right - left为偶数,有符号右移一位,所有位表示的值都缩小2倍(即除以2)。 - 假设
right - left为奇数,二进制末尾为1,右移一位后丢失,相当于计算的是left + right - 1,即实现了向下取整,此时等同于偶数右移。
# JavaScript使用32位按位运算数
JavaScript将数字存储为64位浮点数,但所有按位运算都以32位二进制数执行。在执行位运算之前,JavaScript将数字转换为32位有符号整数,超过32位的数字将丢弃其最高有效位。执行按位操作后,结果将转换回64位JavaScript数。
注意: 上面的例子使用4位无符号二进制数。所以~5返回10。由于JavaScript使用32位有符号整数,JavaScript将返回-6。
00000000000000000000000000000101 // 5
11111111111111111111111111111010 // ~5 = -6
2
十进制转二进制
function dec2bin(dec) {
return (dec >>> 0).toString(2).padStart(32, '0');
}
2
3
有符号整数使用最左边的位作为减号。
# 32位有符号整数(二进制数)
正数
00000000000000000000000000000101 // 5
00000000000000000000000000101000 // 40
2
负数,在计算机中,负数以其正值的补码形式表达。
11111111111111111111111111111011 // -5
11111111111111111111111111011000 // -40
2
# 原码补码与反码
- 原码:原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:
+0和-0),其余位表示数值的大小,数值位就是真值的绝对值。原码不能直接参加运算。 - 反码:反码通常是用来由原码求补码或者由补码求原码的过渡码。正数的反码跟原码是一样;负数时,反码就是原码符号位除外,其他位按位取反。
- 补码:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
无论是十进制还是十六进制的数,在求补码时,都先转化为二进制,再进行补码的转换。
# 十进制数转二进制
- 整数部分:整数除以2取余,直到余数为0,最后把结果倒序。
- 小数部分:小数部分乘以2取整,直到小数部分为0或者位数足够多,结果正序。
(987.7).toString(2) // 1111011011.10110011001100....
987/2 = 493 ··· 1
493/2 = 246 ··· 1
246/2 = 123 ··· 0
123/2 = 61 ··· 1
61/2 = 30 ··· 1
30/2 = 15 ··· 0
15/2 = 7 ··· 1
7/2 = 3 ··· 1
3/2 = 1 ··· 1
1/2 = 0 ··· 1
0.7*2 = 1.4 取整 => 1
0.4*2 = 0.8 取整 => 0
0.8*2 = 1.6 取整 => 1
0.6*2 = 1.2 取整 => 1
0.2*2 = 0.4 取整 => 0
0.4*2 = 0.8 取整 => 0
···
(0.8, 1.6, 1.2, 0.4)循环
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 为什么0.1加0.2不等于0.3
参考文章:为什么0.1加0.2不等于0.3 (opens new window)
for (let i = 0; i < 10; i++) {
for (let j = i; j < 10; j++) {
const right = (i + j) / 10;
const current = i / 10 + j / 10;
if (right !== current) {
console.log(`${i / 10} + ${j / 10} = ${current} !== ${right}`);
}
}
}
// 0.1 + 0.2 = 0.30000000000000004 !== 0.3
// 0.1 + 0.7 = 0.7999999999999999 !== 0.8
// 0.2 + 0.4 = 0.6000000000000001 !== 0.6
// 0.2 + 0.7 = 0.8999999999999999 !== 0.9
// 0.3 + 0.6 = 0.8999999999999999 !== 0.9
// 0.4 + 0.8 = 1.2000000000000002 !== 1.2
// 0.6 + 0.7 = 1.2999999999999998 !== 1.3
// 0.8 + 0.9 = 1.7000000000000002 !== 1.7
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JavaScript遵循IEEE754标准,在64位中存储一个数据的有效数字形式。都是以科学计数法的形式存储的:
64位双精度浮点数 = 1位符号位 + 11位指数位 + 52位尾数位
比如:5000 = 5e3,0.001 = 1e-3,一位小数除了0.5之外其他的都是无限循环小数,无法完全存储,只能存储一个非常近似的值。
0.1+0.2不等于0.3,因为在0.1+0.2的计算过程中发生了两次精度丢失。
- 第一次是在
0.1和0.2转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1,为0则舍去的操作,从而造成一次精度丢失。 - 第二次在
0.1和0.2转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1,为0则舍去的操作,又造成一次精度丢失。
最终导致0.1+0.2不等于0.3。
如何解决:
- 设置一个误差范围值:通常称为“机器精度”,而对于Javascript来说,这个值通常是,而在ES6中,已经为我们提供了这样一个属性:
Number.EPSILON,而这个值正等于。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0。 - 第三方库:
big.js、math.js。 - 参数扩大倍数计算,结果再缩小倍数。
- 使用
Number.prototype.toFixed处理保留小数的位数,但是toFixed采用的是四舍六入五成双 (opens new window)的规则,所以还是有可能出错。可以使用Math.round()处理四舍五入,再缩小相应倍数。
TIPS
Number.prototype.toFixed计算方式:
- 四舍:有效位下一位小于5则舍去。
- 六入:有效位下一位大于5则进1。
- 有效位下一位等于5:找到一个整数n,使得n / - x能够尽可能的接近于0。如果有两个这样的整数n,那么选择更大的那个。
官方解释 (opens new window) a. Let n be an integer for which n / 10**f - x is as close to zero as possible. If there are two such n, pick the larger n.
(2.225).toFixed(2); // 2.23
// 222 / 10**2 - 2.225 => -0.004999999999999893
// 223 / 10**2 - 2.225 => 0.004999999999999893
// n为222或者223时,与0的距离都一样,选择其中更大的223
(12.225).toFixed(2); // 12.22
// 1222 / 10**2 - 12.225 => -0.004999999999999005
// 1223 / 10**2 - 12.225 => 0.005000000000000782
// n为1222时得到的值更接近0
2
3
4
5
6
7
8
9
// 加法
Number.prototype.add = function (arg) {
let r1; let r2;
try { r1 = this.toString().split('.')[1].length; } catch (e) { r1 = 0; }
try { r2 = arg.toString().split('.')[1].length; } catch (e) { r2 = 0; }
const m = Math.pow(10, Math.max(r1, r2));
return (this * m + arg * m) / m;
};
// 减法
Number.prototype.sub = function (arg) {
return this.add(-arg);
};
// 乘法
Number.prototype.mul = function (arg) {
let m = 0;
const s1 = this.toString();
const s2 = arg.toString();
try { m += s1.split('.')[1].length; } catch (e) { }
try { m += s2.split('.')[1].length; } catch (e) { }
return Number(s1.replace('.', '')) * (Number(s2.replace('.', '')) / Math.pow(10, m));
};
// 除法
Number.prototype.div = function (arg) {
let t1 = 0; let t2 = 0;
try { t1 = this.toString().split('.')[1].length; } catch (e) { }
try { t2 = arg.toString().split('.')[1].length; } catch (e) { }
const r1 = Number(this.toString().replace('.', ''));
const r2 = Number(arg.toString().replace('.', ''));
return (r1 / r2) * Math.pow(10, t2 - t1);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33