前言
新手在学习JavaScript时,初次看到这个问题都会去控制台试试,0.1 0.2=?,得到的结果如下:
这个错误的根本原因是:大多数十进制无法正确的转换为二进制。
在计算机上计算时,是将十进制转换为二进制后进行计算,并将计算结果从二进制转换为十进制再次显示。不仅是JavaScript,其他语言如python也是如此。
在JavaScript中主要是因为 Number 类型遵循的是 IEEE 754 标准,使⽤的是 64 位固定⻓度来表示。
IEEE 754 浮点数由三个域组成,分别为 sign bit (符号位)、exponent bias (指数偏移值) 和 fraction (分数值)。64 位中,
sign bit 占 1 位,exponent bias 占 11 位,fraction 占 52 位。
通过公式表示浮点数的值 value = sign x exponent x fraction
当⼀个数为正数,sign bit 为 0,当为负数时,sign bit 为 1.
以 0.1 转换为 IEEE 754 标准表示为例解释⼀下如何求 exponent bias 和 fraction。转换过程主要经历 3 个过程:
- 将 0.1 转换为⼆进制表示
- 将转换后的⼆进制通过科学计数法表示
- 将通过科学计数法表示的⼆进制转换为 IEEE 754 标准表示
将0.1转换为二进制表示
在大学期间学习进制转换时,我们都学过如何转二进制,下面是⼀个数的⼩数部分如何转换为⼆进制。⼀个数的⼩数部分,乘以 2,然后取整数部分的结果,再⽤计算后的⼩数部分重复计算,直到⼩数部分为 0 。
因此 0.1 转换为⼆进制表示的过程如下:
小数 | x2的结果 | 整数部分 |
---|---|---|
0.1 | 0.2 | 0 |
0.2 | 0.4 | 0 |
0.4 | 0.8 | 0 |
0.8 | 1.6 | 1 |
0.6 | 1.2 | 1 |
0.2 | 0.4 | 0 |
0.4 | 0.8 | 0 |
0.8 | 1.6 | 1 |
... | ... | ... |
所以得到 0.1 的⼆进制表示为 0.00011...(⽆限重复 0011) 。
将转换后的二进制通过科学计数法表示
0.00011...(⽆限重复 0011) 通过科学计数法表示则是 1.10011001...(⽆限重复 1001)*2
转换为 IEEE 754 标准表示
当经过科学计数法表示之后,就可以求得 exponent bias 和 fraction 了。
exponent bias (指数偏移值) 等于 双精度浮点数固定偏移值 (2-1) 加上指数实际值(即 2 中的 -4) 的 11 位⼆进制表示。
为什么是 11 位?因为 exponent bias 在 64 位中占 11 位。
因此 0.1 的 exponent bias 等于 1023 (-4) = 1019 的11 位⼆进制表示,即 011 1111 1011。
再来获取 0.1 的 fraction,fraction 就是 1.10011001...(⽆线重复 1001) 中的⼩数位,由于 fraction 占 52位所以抽取 52
位⼩数,1001...(中间有 11 个 1001)...1010 (请注意最后四位,是 1010 ⽽不是 1001,因为四舍五⼊有进位,这个进位
就是造成 0.1 0.2 不等于 0.3 的原因)
0 011 1111 1011 1001...( 11 x 1001)...1010
(sign bit) (exponent bias) (fraction)
此时如果将这个数转换为⼗进制,可以发现值已经变为 0.100000000000000005551115123126 ⽽不是 0.1 了,因此这个计算精度就出现了问题。
如何避免这个问题
在 JavaScript 中避免此错误的唯一方法是四舍五入到所需的小数位数。
如果小数点为一位,则将其乘以 10(如果是两位则为 100),然后将其四舍五入,然后将四舍五入的值除以 10。
var a = 0.1 0.2; // 0.30000000000000004
a = Math.round (a * 10); // 乘以 10 → 舍入 "3.0000000000000004" → "3"
a = a / 10; // “3”除以 10
console.log (a); // 0.3
本文出至:学新通技术网
标签: