惊!为什么 0.1 + 0.2 !== 0.3

因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。

我们都知道计算机表示十进制是采用二进制表示的。

1. 基础

双精度浮点数的结构如下:

1位符号 | 11位指数 | 52位尾数

符号位:0表示正数,1表示负数
指数:采用偏移量为1023的表示法(即实际指数 + 1023)
尾数:标准化后的有效数去掉开头的1,并截取52位。

2. 将十进制小数0.1转换为二进制:

十进制到二进制转换:通过逐步乘以2,取整数部分作为二进制小数部分的下一位,取小数部分继续乘以2。重复此过程直到小数部分为0或达到所需的精度。

0.1 * 2 = 0.2 -> 整数部分为0,小数部分为0.2
0.2 * 2 = 0.4 -> 整数部分为0,小数部分为0.4
0.4 * 2 = 0.8 -> 整数部分为0,小数部分为0.8
0.8 * 2 = 1.6 -> 整数部分为1,小数部分为0.6
0.6 * 2 = 1.2 -> 整数部分为1,小数部分为0.2
0.2 * 2 = 0.4 -> 整数部分为0,小数部分为0.4
......

这个过程会继续下去,结果是一个无限循环小数:0.00011001100110011001100110011...

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 1.1001100110011001100110011001100110011001100110011001×2^−4

3. 将十进制小数0.2转换为二进制:

0.2 * 2 = 0.4 -> 整数部分为0,小数部分为0.4
0.4 * 2 = 0.8 -> 整数部分为0,小数部分为0.8
0.8 * 2 = 1.6 -> 整数部分为1,小数部分为0.6
0.6 * 2 = 1.2 -> 整数部分为1,小数部分为0.2
0.2 * 2 = 0.4 -> 整数部分为0,小数部分为0.4
0.4 * 2 = 0.8 -> 整数部分为0,小数部分为0.8

那么 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 1.1001100110011001100110011001100110011001100110011001×2^−3

4. 具体计算

  1. 对齐指数
    将0.1的尾数右移一位对齐0.2的指数:
0.1 尾数(右移一位): 0.110011001100110011001100110011001100110011001100110011001100
0.2 尾数:           1.100110011001100110011001100110011001100110011001100110011001
  1. 尾数相加
    将对齐后的尾数相加:
  0.110011001100110011001100110011001100110011001100110011001100
+ 1.100110011001100110011001100110011001100110011001100110011001
---------------------------------------------------------------
 10.011001100110011001100110011001100110011001100110011001100101

由于结果超过了1,我们需要规范化:

1.001100110011001100110011001100110011001100110011001100110010 × 2^{-2}

尾数部分为:1.001100110011001100110011001100110011001100110011001100110010

  1. 将尾数部分转换成十进制:
1+ 0×2^1 
 + 0×2^2 
 + 1×2^3
 + 1×2^4
 + 0×2^5
 + 0×2^6
 + 1×2^7
 + 1×2^8
 +=
1 + 1/8
  + 1/16
  + 1/128
  + 1/256
  + 1/2048
  + 1/4096
  + 1/32768
  + 1/65536
  + 1/524288
  +=
1 + 0.125
  + 0.0625
  + 0.0078125
  + 0.00390625
  + 0.00048828125
  + 0.000244140625
  + 0.000030517578125
  + 0.0000152587890625
  + 0.0000019073486328125
  + …
≈ 1.1999999999999999555910790149937383830547332763671875

最终结果

1.1999999999999999555910790149937383830547332763671875 x 2^{-2}
= 1.1999999999999999555910790149937383830547332763671875 / 40.3000000000000000444089209850062616169452667236328125

5. 解决办法

  1. 固定小数位数比较
    将浮点数运算结果保留一定的小数位数,然后进行比较:
function isEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}

const result = 0.1 + 0.2;
console.log(isEqual(result, 0.3)); // 输出: true
  1. 转换成整数进行计算
    将浮点数转换为整数进行计算,确保运算的精度:
function add(a, b) {
  const factor = Math.pow(10, 10); // 选择足够大的因子
  return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}

const result = add(0.1, 0.2);
console.log(result === 0.3); // 输出: true
  1. 指定小数位数进行计算
parseFloat((0.1 + 0.2).toFixed(10))
  1. 使用Big.js第三方库
const a = new Big(0.1);
const b = new Big(0.2);
 
const sum = a.plus(b); // 加法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值