十进制与二进制的本质区别
十进制中的有限小数
在十进制中,一个分数能写成有限小数的条件是:分母只包含 2 和 5 的因子
// 十进制有限小数的条件:分母 = 2^m × 5^n
1/2 = 0.5 // 分母 2
1/4 = 0.25 // 分母 4 = 2²
1/5 = 0.2 // 分母 5
1/8 = 0.125 // 分母 8 = 2³
1/10 = 0.1 // 分母 10 = 2 × 5
// 无限循环小数
1/3 = 0.3333... // 分母 3 包含因子 3
1/6 = 0.1666... // 分母 6 = 2 × 3,包含因子 3
1/7 = 0.142857... // 分母 7
二进制中的有限小数条件
在二进制中,一个分数能写成有限小数的条件是:分母必须是 2 的幂
// 二进制有限小数:分母 = 2^n
1/2 = 0.1₂ // 十进制 0.5
1/4 = 0.01₂ // 十进制 0.25
1/8 = 0.001₂ // 十进制 0.125
3/4 = 0.11₂ // 十进制 0.75
// 二进制无限循环小数
1/10 = 0.0001100110011...₂ // 十进制 0.1
1/5 = 0.001100110011...₂ // 十进制 0.2
0.1 的二进制转换过程
手动计算 0.1 转二进制(乘2取整法)
0.1 × 2 = 0.2 → 整数部分 0,取 0
0.2 × 2 = 0.4 → 整数部分 0,取 0
0.4 × 2 = 0.8 → 整数部分 0,取 0
0.8 × 2 = 1.6 → 整数部分 1,取 1,剩下 0.6
0.6 × 2 = 1.2 → 整数部分 1,取 1,剩下 0.2
0.2 × 2 = 0.4 → 整数部分 0,取 0
0.4 × 2 = 0.8 → 整数部分 0,取 0
0.8 × 2 = 1.6 → 整数部分 1,取 1,剩下 0.6
0.6 × 2 = 1.2 → 整数部分 1,取 1,剩下 0.2
... 开始循环 (0011 循环)
结果:0.1₁₀ = 0.0001100110011...₂(0011 无限循环)
验证循环
0.1₁₀ = 0.0001100110011...₂
↑↑↑↑ ↑↑↑↑
0011 0011 循环
0.2 的二进制转换
计算过程
0.2 × 2 = 0.4 → 整数 0
0.4 × 2 = 0.8 → 整数 0
0.8 × 2 = 1.6 → 整数 1,剩下 0.6
0.6 × 2 = 1.2 → 整数 1,剩下 0.2
0.2 × 2 = 0.4 → 整数 0 (开始循环)
结果:0.2₁₀ = 0.001100110011...₂(0011 循环)
注意:0.2 的二进制就是 0.1 的二进制左移一位
0.1 = 0.0001100110011...
0.2 = 0.001100110011... (相同模式,只是起始位置不同)
数学证明
为什么 0.1 是无限循环?
0.1 = 1/10
在二进制中:
1/10 = 1/(2 × 5) = (1/2) × (1/5)
1/5 在二进制中无法用有限位数表示,因为分母 5 不是 2 的幂
实际上:
1/5 = 0.2₁₀ = 0.001100110011...₂
证明:
设 x = 0.001100110011...₂
则 2⁴x = 100.1100110011...₂ = 4 + (x - 0.0000110011...)
整理可得 x = 4/15 = 0.2666...?
更严谨的推导:
设 x = 0.001100110011...₂ (二进制)
这等于:1/8 + 1/16 + 1/128 + 1/256 + ...
= (1/8 + 1/16) × (1 + 1/16 + 1/256 + ...)
= (3/16) × (1/(1 - 1/16))
= (3/16) × (16/15)
= 3/15 = 1/5 ✅
计算机中的实际存储
0.1 在内存中的近似值
// 查看 0.1 的实际存储值
console.log(0.1.toFixed(20)) // 0.10000000000000000555
// 比较 0.1 和它的近似值
console.log(0.1 === 0.10000000000000000555) // true
实际存储的二进制近似:
0.1 ≈ 0.0001100110011001100110011001100110011001100110011001101₂
(52位尾数,第53位四舍五入)
误差累积
// 0.1 的存储误差
let exact = 0.1
let stored = 0.10000000000000000555
let error = stored - exact // ≈ 5.55e-17
// 多次运算导致误差放大
let sum = 0
for (let i = 0; i < 10; i++) {
sum += 0.1
}
console.log(sum) // 0.9999999999999999 (不是 1.0)
其他常见例子
// 0.3 的二进制
// 0.3 = 3/10 = 同样无限循环
console.log(0.1 + 0.2) // 0.30000000000000004
// 0.125 是有限的 (1/8)
console.log(0.125.toString(2)) // "0.001"
// 0.3 是无限的
console.log((0.3).toFixed(20)) // 0.29999999999999998890
// 0.5 是有限的
console.log(0.5.toString(2)) // "0.1"
可视化理解
十进制中能精确表示的分数:
1/2, 1/4, 1/5, 1/8, 1/10, 3/4, 7/8, 3/5...
二进制中能精确表示的分数:
1/2, 1/4, 1/8, 1/16, 1/32, 3/4, 5/8, 7/8...
交集(十进制和二进制都能精确表示):
1/2, 1/4, 1/8, 3/4, 5/8, 7/8...
这些分母只能是 2 的幂(在约分后)
综上所述:
根本原因:十进制和二进制使用不同的基数(10 vs 2),导致同一个有理数在一个进制中是有限小数,在另一个进制中可能是无限循环小数。
- 0.1 = 1/10,分母 10 的质因数包含 5,不是 2 的幂 → 二进制无限循环
- 0.2 = 1/5,分母 5 不是 2 的幂 → 二进制无限循环
- 0.5 = 1/2,分母是 2 → 二进制有限小数