# 从0.1 + 0.2 !== 0.3 聊聊计算机基础

English

## 表面工作

• 计算机二进制的表现形式以及二进制的计算方式？
• 什么是原码、补码、反码、移码，都是用来做什么的？

## 第一个前置知识，二进制

### 二进制的表现形式

• 10进制的 1 在计算机中表示为 1
• 10进制的 2 在计算机中表示为 10
• 10进制的 8 在计算机中表示为 1000
• 10进制的 15 在计算机中表示为 1111

### 二进制的计算方式

#### 整数部分的二进制计算

``````30 % 2 ········· 0
15 % 2 ········· 1
7 % 2 ········· 1
3 % 2 ········· 1
1 % 2 ········· 1
0``````

``````100 % 2 ········· 0
50 % 2 ········· 0
25 % 2 ········· 1
12 % 2 ········· 0
6 % 2 ········· 0
3 % 2 ········· 1
1 % 2 ········· 1
0``````

``````function getBinary(number) {
const binary = [];
function execute(bei) {
if (bei === 0) {
return ;
}
const next = parseInt(bei / 2, 10);
const yu = bei % 2;
binary.unshift(yu);
execute(next);
}
execute(number);
return binary.join('');
}
console.log(getBinary(30)); // 11110
console.log(getBinary(100)); // 1100100``````

``````1100100
= 1 * 2^6 + 1 * 2^5 + 0 * 2^4 + 0 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0
= 100``````

``````function getDecimal(binary) {
let number = 0;
for (let i = binary.length - 1; i >= 0; i--) {
const num = parseInt(binary[binary.length - i - 1]) * Math.pow(2, i);
number += num;
}
return number;
}
console.log(getDecimal('11110')); // 30
console.log(getDecimal('1100100')); // 100``````

#### 小数部分的二进制计算

``````0.0625 * 2 = 0.125 ········· 0
0.125 * 2 = 0.25  ········· 0
0.25 * 2 = 0.5   ········· 0
0.5 * 2 = 1.0   ········· 1``````

``````function getBinary(number) {
const binary = [];
function execute(num) {
if (num === 0) {
return ;
}
const next = num * 2;
const zheng = parseInt(next, 10);
binary.push(zheng);
execute(next - zheng);
}
execute(number);
return '0.' + binary.join('');
}
console.log(getBinary(0.0625)); // 0.0001``````

``````0.0001
= 0 * 2^-1 + 0 * 2^-2 + 0 * 2^-3 + 1 * 2^-4
= 0.0625``````

``````function getDecimal(binary) {
let number = 0;
let small = binary.slice(2);
for (let i = 0; i < small.length; i++) {
const num = parseInt(small[i]) * Math.pow(2, 0 - i - 1);
number += num;
}
return number;
}
console.log(getDecimal('0.0001')); // 0.0625``````

<details>
<summary> 18.625 的二进制表示是什么 ？？？ => 点击查看详情</summary>
<pre>

``````18的二进制表示为: 100010
0.625的二进制表示为: 0.101

</pre>
</details>
<sammry></sammry>

## 第二个前置知识，计算机码

``V = (-1)^s * M * 2^E``
• 其中s为符号位，0为正数，1为负数；
• M为尾数，是一个二进制小数，其中规定第一位只能是1，1和小数点省略
• E为指数，或者称为阶码

### 原码

• +1 = [000 0000 0001]原
• -1 = [100 0000 0001]原
因为第一位是符号位，所以其取值区间为[111 1111 1111, 011 1111 1111] = [-1023, 1023];

### 反码

什么是反码，反码是在原码的基础上进行反转。正数的反是其本身；负数的反码是符号位不变，其余位取反。

• +1 = [000 0000 0001]原 = [000 0000 0001]反
• -1 = [100 0000 0001]原 = [111 1111 1110]反

### 补码

• +1 = [000 0000 0001]原 = [000 0000 0001]反 = [000 0000 0001]补
• -1 = [100 0000 0001]原 = [111 1111 1110]反 = [111 1111 1111]补
为什么会有补码这玩意呢？
• 首先在计算机中是没有减法的，都是加法，比如 1 - 1 在计算机中是 1 + (-1).
• 如果使用原码进行减法运算：

``````1 + (-1) = [000 0000 0001]原 + [100 0000 0001]原
= [100 0000 0010]原
= -2``````

===>>> 结论：不对

• 为解决这个不对的问题于是就有了反码去做减法：

``````1 + (-1) = [000 0000 0001]反 + [111 1111 1110]反
= [111 1111 1111]反
= [100 0000 0000]原
= -0``````

发现值是正确的，只是符号位不对；虽然+0和-0在理解上是一样的，但是0带符号是没有意义的，况且会出现 [000 0000 0000]原 和 [100 0000 0000]原 两种编码方式。

===>>> 结论：不大行

• 为解决上面这个符号引起的问题，就出现了补码去做减法：

``````1 + (-1) = [000 0000 0001]补 + [111 1111 1111]补
= [000 0000 0000]补
= [000 0000 0000]原
= 0``````

这样得到的结果就是完美的了，0用 [000 0000 0000] 表示，不会出现上面 [100 0000 0000]。

===>>> 结论：完美

### 移码

• +1 = [000 0000 0001]原 = [000 0000 0001]反 = [000 0000 0001]补 = [100 0000 0001]移
• -1 = [100 0000 0001]原 = [111 1111 1110]反 = [111 1111 1111]补 = [011 1111 1111]移
细心一点可以发现规律：
• +1 = [000 0000 0001]原 = [100 0000 0001]移
• -1 = [100 0000 0001]原 = [011 1111 1111]移

## 为什么 0.1 + 0.2 !== 0.3 ?

``````0.1 = 0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 .... 0011无限循环
0.2 =  0. 0011 0011 0011 0011 0011 0011 0011 0011 0011 .... 0011无限循环``````

``````0.1 = (-1)^ 0 * 1.1 0011 0011 0011 * 2^(-4)
-4 = 10 0000 0100``````

``````V = (-1)^S * M * 2^E
S = 0  // 1位，正数为0，负数为1
E = [100 0000 0100]原 // 11位
= [111 1111 1011]反
= [111 1111 1100]补
= [011 1111 1100]移
M = 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 // 52位(其中1小数点省略)``````

``````0.2 = (-1)^ 0 * 1.1 0011 0011 0011 * 2^(-3)
-4 = 100 0000 0011

V = (-1)^S * M * 2^E
S = 0  // 1位，正数为0，负数为1
E = [100 0000 0011]原 // 11位
= [111 1111 1100]反
= [111 1111 1101]补
= [011 1111 1101]移
M = 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 // 52位(其中1小数点``````

### 对阶

• 大的阶码向小的阶码看齐，就需要把大的阶码的数的尾数向左移动，此时就有可能在移位过程中把尾数的高位部分移掉，这样就引发了数据的错误。这是不可取的
• 小的阶码向大的阶码看齐，就需要把小的阶码的数向右移动，高位补0；这样就会把右边的数据给挤掉，这样也就导致了会影响数据的精度，但是不会影响数据的整体大小。

``````// 0.1
E = [100 0000 0011]原 = [111 1111 1100]反 = [111 1111 1101]补 // 11位, 对阶后
M = 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 // 移动前
M = 0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 // 移动后
// 0.2 保持不变
E = [100 0000 0011]原 = [111 1111 1100]反 = [111 1111 1101]补 // 11位，不变
M = 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 // 52位，不变``````

### 真值计算

``````0.1 = 0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 .... 0011无限循环
0.2 = 0. 0011 0011 0011 0011 0011 0011 0011 0011 0011 .... 0011无限循环
0.1 + 0.2
= 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 (1001...舍弃)
+ 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0011...舍弃)
= 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 (1100...舍弃)
= 0.2999999999999998``````

``0.1 + 0.2 = 0.30000000000000004``

``````0.1 + 0.2
= 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 (1001...舍弃)
+ 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0011...舍弃)
= 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 (1100...舍弃)
= 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 （入1)
= 0.30000000000000004``````

114 声望
588 粉丝
0 条评论