面试官又问我 0.1 + 0.2 不等于 0.3?

遇到的问题

计算小数的时候,有的时候会遇到反直觉的数值计算现象:

ini 复制代码
console.log(0.3 - 0.2 == 0.1) // false
console.log(0.25 + 0.25 == 0.5) // true

这是因为什么呢?读完这篇文章将展示浮点数的存储原理,瞅瞅他的本质是什么!

二进制存储的本质

归根结底,是因为计算机再存储的时候和我们想想的不一致。

整数部分

我们知道所有数字在计算机中都以二进制形式存储。对于整数,转换相对简:

  • 十进制1 → 二进制01
  • 十进制5 → 二进制101
  • 十进制7 → 二进制111

小数部分

那小数位置是怎么存储的呢? 是不是也按照这种存储呢? 假如也按照整数存储的方式:3.3 ---- 11.11

我们知道十进制 3.3 + 3.3 = 6.6, 如果小数方面同样这样计算 11.11 + 11.11 = 111.10 转换为10进制就变成了 7.1 ; 这很明显是不对的。

现代计算机遵循IEEE 754浮点数标准,采用三部分存储:

  1. 符号位(1bit)
  2. 指数位(8/11bit)
  3. 尾数位(23/52bit)

这种设计类似于科学计数法:数值 = 符号 × 尾数 × 2^指数

十进制

  • 314 = 310^2 + 110^1 + 4*10^0
  • 3.14 = 310^0 + 110^(-1) + 4*10^(-2)

二进制

  • 101 = 12^2 +02^1 + 1*2^0 = 5
  • 1.01 = 12^0 + 1 2^(-1) + 02^(-2) + 12^(-3) = 1.625

用这种方式存储会有什么问题呢?

十进制转换为二进制,必须满足分母是2的幂次

会发现二进制使用这种方式存储,他的最后一位一定是5。

ini 复制代码
0.5 = 1/2 → 二进制0.1(精确存储)
0.25 = 1/4 → 二进制0.01(精确存储)
0.1 = 1/10 → 二进制无限循环0.000110011...(无法精确存储)

如果给一个十进制的最后一个数字不是5,那么他一定转换不出来有限位数的二进制

vbscript 复制代码
0.3.toString(2)
// '0.010011001100110011001100110011001100110011001100110011'

0.2.toString(2)
// '0.001100110011001100110011001100110011001100110011001101'

0.5.toString(2)
'0.1'

这也就解释了为什么浮点数计算有时候正确,有时候不正确了。

那么我们工作中遇到这类的问题该怎么正确计算呢?

实践

  1. 转换为整数计算
ini 复制代码
const price1 = 0.1;
const price2 = 0.2;
const scaled1 = price1 * 100; // 10
const scaled2 = price2 * 100; // 20
const result = (scaled1 + scaled2) / 100; // 0.3

缺陷:需要手动处理放大的倍数。

  1. 使用toFixed()控制显示精度
ini 复制代码
const sum = 0.1 + 0.2; // 0.30000000000000004
const fixedSum = sum.toFixed(2); // "0.30"
const numSum = parseFloat(fixedSum); // 0.3(转换为数值)

缺陷:因为是四舍五入,所以到底也不是精确,如果仅仅是作为展示可以使用。

  1. 使用第三方计算库(如 decimal.jsbig.js):
vbnet 复制代码
// 使用 decimal.js
import Decimal from "decimal.js";
const sum = new Decimal(0.1).plus(new Decimal(0.2)).toString(); // "0.3"
  1. 处理动态小数,动态计算放大倍数:
ini 复制代码
function getMaxDecimalPlaces(...nums) {
  return Math.max(...nums.map(num => {
    const str = num.toString().split('.')[1] || '';
    return str.length;
  }));
}

const nums = [0.1, 0.02];
const maxDecimals = getMaxDecimalPlaces(...nums); // 2
const scale = 10 ** maxDecimals;
const scaledSum = nums.reduce((sum, num) => sum + num * scale, 0);
const result = scaledSum / scale; // 0.12
相关推荐
知识分享小能手2 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(上)——知识点详解 + 案例实战(4)
前端·javascript·学习·微信小程序·小程序·html5·微信开放平台
清灵xmf3 小时前
CSS field-sizing 让表单「活」起来
前端·css·field-sizing
文火冰糖的硅基工坊3 小时前
[光学原理与应用-480]:《国产检测设备对比表》
前端·人工智能·系统架构·制造·半导体·产业链
excel3 小时前
Qiankun 子应用生命周期及使用场景解析
前端
weixin_446260853 小时前
Django - 让开发变得简单高效的Web框架
前端·数据库·django
蓝莓味的口香糖3 小时前
【JS】什么是单例模式
开发语言·javascript·单例模式
ObjectX前端实验室4 小时前
【react18原理探究实践】异步可中断 & 时间分片
前端·react.js
SoaringHeart4 小时前
Flutter进阶:自定义一个 json 转 model 工具
前端·flutter·dart
努力打怪升级4 小时前
Rocky Linux 8 远程管理配置指南(宿主机 VNC + KVM 虚拟机 VNC)
前端·chrome
brzhang5 小时前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构