
我们都知道二进制是0和1组成的,在二进制被
戈特弗里德·莱布尼茨
设计出来的时候,据说他学习参考了中国的<易经>,<易经>中的太极生两仪,两仪生四象,思象生八卦,八卦生十六卦,十六生32卦,32卦生64卦.像不像我们的二进制计数呢 1 2 4 8 16 32 64,好了,我们进入正题
- 二进制在IEEE 754 64 中的存储格式是什么?
- 位操作都有哪些?
- 二进制的加减乘除
- 二进制和十进制,八进制,十六进制的互相转换
二进制在IEEE 754 64 中的存储格式是什么?
ECMAScript
中所有的值都以 IEEE 754 64
位格式存储,但是位操作并不直接应用到64位,而是先把值转换为32位再进行位操作,之后再把结果转换为64位.对于开发者而言,就好像只有32位,因为64位整数存储格式是不可见的.既然知道了这个,那么就只需要考虑32位整数的就可以了
那么32位的整数又是怎么使用的呢?
有符号整数使用32位的前31位表示整数值,第32位表示数值的符号,如0表示正,1表示负,这个位称为符号位,它的值决定了数值其余部分的格式.
正值的存储
正值以真正的二进制格式存储,即31位中的每一位都代表2的幂.第一位(成为0位)表示2**0
,第二位表示2**1
,第三位就是2**2
以此类推.如果一个位是空的,则以0填充,相当于忽略不计.比如数值18的二进制格式为0000 0000 0000 0000 0000 0000 0001 0010
或者精简为10010
.后者是用5个有效位,来决定实际的值.

负值的存储
负值以一种成为二补数(或补码)
的二进制编码存储.一个数值的二补数通过以下三个步骤来完成.
- 确定绝对值的二进制表示,如对于-18就是先确定18的二进制表示
- 找到数值的反码(补数),就是把所有的0变成,所有的1变成0
- 最后,给结果加1
就完成了.
我们实际操作一下:
第一步:-18的绝对值18的二进制是
0000 0000 0000 0000 0000 0000 0001 0010
第二步:取反,把每个0变成1,每个1变成0
1111 1111 1111 1111 1111 1111 1110 1101
第三步:最后给这个值加个1
1111 1111 1111 1111 1111 1111 1110 1110
这个就是我们的-18
位操作都有哪些?
1.按位非 ~
按位非操作符用波浪符~
表示,它的作用是返回数值的一补数,例如:
let num1 =25 ; // 0000 0000 0000 0000 0000 0000 0001 1001
let num2 = ~num1; // 1111 1111 1111 1111 1111 1111 1110 0110
console.log(num2); // -26
通过上面的结果可以看出 按位非的最终结果是对数值取反并减-
相当于
let num1 = 25;
let num2 = -num1 - 1;
虽然效果一样,但是未运算要快的多.
2.按位与 &
按位与操作符使用&
表示.
第一数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
就是 两个1才返回1 任何一位是0 就返回0 这个不就是乘法吗? 1 * 1 = 1 ,1 * 0 = 0 ,0 * 0 = 0
例如:25 & 3
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
3.按位或 |
用管道符表示 只要1 就返回1 两个是0 就返回0
第一数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
还是 25 | 3 ,我们看看值是多少呢?
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
4.按位异或 ^
按位异或和按位或的区别是 ,它只在一位上是1的时候才返回1 ,两个是1的时候反而是0 ,当然两个1的时候也是0
---|---|--
1 | 1 | 1
1 | 0 | 1
0 | 1 | 1
0 | 0 | 0
25 ^ 3 = ?
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.
5.左移 <<
会按照指定的位数将数值的所有位向左移动.比如,如果数值2 (等于二进制是10),就会得到64(二进制2000000)
let oldValue = 2; // 10
let newValue = oldValue << 5; //1000000
注意:在移位后,数值右端会空出5位,左移会以0填充这些空位,让结果是完整的32位数值.
注意:左移会保留它所操作数值的符号.所以-2 << 5 就是 -64
6.有符号右移 >>
会将数值的所有32位都向右移..同时会保留符号位(整数还是负数),有符号右移其实就是左移的逆运算.
例如:64 >> 5 === 2
0000 0000 0000 0000 0000 0000 0010 0000
---------------------------------------
0000 0000 0000 0000 0000 0000 0000 0010
如果移位的值超过它自己的值会怎么样,例如2 >> 5 ,它的值是0
移位出现的空值会补0
7.无符号右移 >>>
正数
无符号右移和有符号右移相同
负数
对于负数,有时候差异会非常大,与有符号右移不同,无符号右移会给空位补零,而不管符号位是什么.
对于正数,当然没有没问题.但是对于负数,就差距很大了.无符号右移操作符将负数的二进制表示当成整数的二进制
表示处理.因为负数时其绝对值的二补数,所以右移之后结果边得非常大.
let oldValue = -64;// 1111 1111 1111 1111 1111 1111 1111 1100 0000
let newValue = oldValue >>> 5;//等于十进制134217726
-------------------------------------------------------------------
>>> 0000 1111 1111 1111 1111 1111 1111 1111 1110
就是 134217726
二进制的加减乘除
二进制的加法 逢 2 进 1
还记得小学的时候我们学过的10进制加减乘除法是怎么算的吗?就是他们两个数竖着放在一起,各位个位对着个位,十位对着十位.百位对着百位.
然后最重要的是,10进制它是逢10进1.而二进制它的区别是逢2进1,例如:我们 3 +1
0 0 1 1
+ 0 0 0 1
---------
0 1 0 0
1 + 1 等于 2 逢2 进1 然后 第二位 已经有了一个1 我们两个1 相加又进1 而自己则是 0 ,所以 是 0 1 0 0
再看一个
0 1 0 1
+ 0 0 1 1
----------
1 0 0 0
转换成10进制就是 5+3 = 8;
二进制的减法 不够借 2
和二进制的加法的算法一样,我们把他们竖排,在10进制里面进行减法运算的时候有一个概念是 不够10的则向上一位借10做减法.
在二进制中也一样,如果不够则从上一位借2 ,也就是 不够借2
0 1 0 1
+ 0 0 1 1
----------
0 0 1 0
在运算的时候,第一位 1 - 1 = 0 ,第二位 0 -1 不够 ,怎么办,向上一位借1 啊,就是 2 - 1 ,剩下 1
所以最后就是 0 0 1 0
转换为10进制就 5 - 3 = 2
结果也是相同
二进制的乘法 1 * 1 = 1 , 0 * 1 = 0
还记得10进制乘法是怎么算的吗?
7 8
5 6
----------------
4 8
4 2
4 0
3 5
---------------
4 3 6 8
10进制的关键是 每一位和上一个数的每一位想乘,所有的都遍历完成以后,在相加,逢10进1
0 1 0 1
+ 0 0 1 1
----------
0 1 0 1
0 1 0 1
----------
0 1 1 1 1
5 * 3 = 15
二进制的乘法是1 * 1 = 1 1 * 0 = 0,计算完毕以后进行相加. 逢 2 进 1
二进制的除法
二进制的除法和十进制的很像,都需要不够借1
0 0 0 1 1
-----------
0 1 0 1 | 0 1 1 1 1
0 1 0 1
-----------
1 0 1
1 0 1
-----
0
最后是 0 1 1
是不是和10进制的除法一样.
二进制和十进制,八进制,十六进制的互相转换
二进制转换成十进制
二进制转换成十进制非常简单,我们知道二进制实际上是表示的2的幂
我们以八位为例,这里直接用幂计算符号 **来表示
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
2**7 | 2**6 | 2**5 | 2**4 | 2**3 | 2**2 | 2**1 | 2**0 |
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
比如我们上面的数 0 1 0 1
对应到上面的值 22 + 20 也就是 4 + 1 = 5
按照位数对应到它的值,我们就能求出来.
十进制转换成二进制
同样可以参考上面的表格来计算.
比如15 ,15这个值比16小,所以它应该是 8+4+2+1 = 15
所以它的值就是
16 8 4 2 1
0 1 1 1 1
如果是16就直接用16数值那里的1
16 8 4 2 1
1 0 0 0 0
是不是很简单
二进制转八进制
以三位作为划分,我们按 倒排4 2 1 来标识,三排作为一个数字,因为8进制是逢8进1,所以它的值就是按照 最高位来排下来.
1 0 0 1 1 0
----- -----
4 2 1 4 2 1
--------------
4 6
-------------
46
八进制数就是 46
我们在看一个例子
0 1 0 0 1 1
----- -----
4 2 1 4 2 1
---------------
2 3
最后得到的值是23(8)八进制数
那么带小数的是怎么算的呢?
0 1 1 . 0 1 1
---- -----
4 2 1 4 2 1
--------------
3 3
最后得到的值就是 3.3(8)八进制数
八进制 转 二进制
实际上和10进制转二进制很像
我们知道一个八进制数需要三个二进制数来表示
例如 八进制数 46(8)
4 6
--- ---
4 2 1 4 2 1
----- -----
1 0 0 1 1 0
最后转换出来就是 100 110,带小数点也一样计算.
二进制 转 十六进制
十六进制的前9位是正常的1~9
,从10 开始
A --> 10
B --> 11
C --> 12
D --> 13
E --> 14
F --> 15
我们上面的八进制是需要三个二进制数来表示,16进制,就需要4个二进制数来表示.
例如:
1 1 1 0 1 1 0 0
------- -------
8 4 2 1 8 4 2 1
----------------
14 12
----------------
E C
最后就是 0xEC
parseInt(0xEC).toString(2)
//'11101100'
可以看到是正确的
同样我们也可以推理出十六进制转二进制的做法
十六进制 转 二进制
十六进制参考八进制数据转换
E C
14 12
------- -------
8 4 2 1 8 4 2 1
------- -------
8+4+2 8+4
------- -------
1 1 1 0 1 1 0 0
最后转换成 二进制就是 1110 1100
Over...
网友评论