关于javascript数字类型的底层表示和位运算

一. 前言

javascript有6种基本类型(String,Number,Boolean,Null, Undefined,Symbol)。

其中Number是表示数字的唯一类型。C++有int,float,double, unsigned等各种数字类型,但是js只有一种数字类型,那就是Number。

在js中所有的数字都是双倍精度的浮点数类型,也就是c++中的double类型,每个数字使用64位(8个字节)存储。但是做有些运算的时候(位运算,parseInt()等 ),是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。

二. 浮点型数字表示

查看浮点型数字存储表示

理解js 里面的 MAX_VALUE MIN_VALUE MAX_SAFE_INTEGER MIN_SAFE_INTEGER

注意:这些整数中可以连续、准确地表示的那些整数称为 安全的整数

三. 位运算(32位有符号整数)

位运算会先把数字(不管是2.1这样的小数, 还是2这样的整数)转换成32位有符号整数,然后再做操作,返回的也是32位有符号整数。

所有的按位操作运算符都会被转成补码(正数的补码和原码相同,负数的补码是原码除符号位取反之后再+1)

用二进制表示一个数字,一般我们习惯用原码表示。但是在运算的时候计算机会使用每个数字的补码表示来做运算

1
2
a>>b 有符号右移: 右移过程中保留符号位,左侧填充符号位,右侧被移出的位被丢弃。
效果:正数返回正数,负数返回负数。效果类似于除以2或者4或者8
1
2
a>>>b 无符号右移: 右移过程中不管符号位是什么,左侧填充0,右侧被移出的位被丢弃。
效果:返回的都是正数。(对于正数效果是除以2/4/8,对于负数返回了一个正数,不知道这个正数有啥用)
1
2
a<<b 左移: 左移不区分是否有符号,右侧填充0,左侧被移出的位被丢弃。
效果:正数返回正数,负数返回负数。效果类似于乘以2或者4或者8
1
2
~a 按位非(NOT):对数字的补码每一位取反(0变1,1变0)
效果:结果是a+1之后的相反数,比如~(-2) === 1,~(-1) === 0
1
2
a^b 按位异或(XOR): 按位将 0和1作用时, 不一样的得到1, 一样的得到0
效果:将任一数值 x 与 0 进行异或操作,其结果为 x。将任一数值 x 与 -1 进行异或操作,其结果为 ~x。
1
2
a | b 按位或(OR): 按位将 0和1作用时, 只要有一个1则返回1,否则返回0
效果:将任一数值 x 与 0 进行按位或操作,其结果都是 x。将任一数值x与-1进行按位或操作,其结果为 -1。
1
2
a & b 按位与(AND): 按位将 0和1作用时, 必须两个都为1才返回1,否则返回0
效果:将任一数值x与0执行按位与操作,其结果都为 0。将任一数值 x 与 -1 执行按位与操作,其结果都为 x。

上面操作之所以很多都与0和-1操作,是因为0在内存的补码表示全部是0,-1在内存的补码表示全部都是1。

0在内存的补码表示全部是0:00000000

-1在内存的补码表示全部都是1: 11111111

四. 位运算在前端中的运用

1 使用 & 运算判断 奇数偶数

1
2
3
4
5
6
// 偶数 & 1 = 0
// 奇数 & 1 = 1
console.log(2 & 1) // 0
console.log(3 & 1) // 1
console.log(-2 & 1) // 0
console.log(-3 & 1) // 1

2 使用 ~, >> , >>> , << , | 来取整

1
2
3
4
console.log(6.83 >> 0)  // 6
console.log(6.83 << 0) // 6
console.log(-6.83 << 0) // -6
console.log(-6.83 >> 0) // -6
1
2
// >>>不可对负数取整
console.log(6.83 >>> 0) // 6
1
2
console.log(~~ 6.83)    // 6
console.log(~~ -6.83) // 6

这里都是利用了运算会把数字变成32位有符号整数的特点

3 使用^来完成值交换

1
2
3
4
5
6
7
var a = 5
var b = 8
a ^= b
b ^= a
a ^= b
console.log(a) // 8
console.log(b) // 5