前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库JavaScript 浮点数精度 0.1+0.2
JavaScript值与类型

为什么 0.1 + 0.2 不等于 0.3?浮点数该怎么比较?

JavaScript 的数字都是 IEEE 754 双精度浮点数,0.1 和 0.2 在二进制里是无限循环小数,只能存一个近似值,两个近似值相加就得到 0.30000000000000004。比较浮点数不要用 ===,而是看两数之差是否小于一个容差,金额则转成整数分来算。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#值与类型
先看核心答案读代码示例
理解线索

浮点误差的来龙去脉

  1. 二进制表示分母不是 2 的幂的小数,在二进制里都会无限循环
  2. 53 位有效位双精度只有 53 位有效二进制位,超出部分按就近舍入
  3. Number.EPSILON1 和比 1 大的最小浮点数之差,约 2.22e-16

Number.EPSILON 只是 1 附近的间距。数字越大,相邻两个浮点数隔得越远,直接拿它当固定阈值去比 1000 左右的数会误判。

核心回答

先记住这个答案

JavaScript 的 Number 统一用 IEEE 754 双精度浮点数存储,只有 53 位有效二进制位。0.1 和 0.2 换成二进制都是无限循环小数,存储时只能截断成最接近的近似值。0.1 实际存的比 0.1 大一点,0.2 也大一点,两个误差加起来,结果比 0.3 的近似值还要大一档,于是得到 0.30000000000000004。比较浮点数时要判断两者之差是否足够小,金额这类必须精确的场景干脆转成整数分再算。

  • 0.1 在二进制里是无限循环小数,只能近似存储
  • 双精度浮点都可能出现相同误差
  • 比较用容差,容差要随数值大小缩放
  • 金额优先用整数最小单位计算

误差到底从哪来

十进制的 1/3 写不尽,只能写成 0.3333…。二进制也一样,凡是分母不是 2 的幂的小数都写不尽。0.1 就是 1/10,分母里有 5,所以在二进制里是 0.000110011001100… 无限循环。

看看内存里真实存的值JavaScript
console.log(0.1 + 0.2)
console.log(0.1 + 0.2 === 0.3)
// toPrecision(20) 显示内存里真实存的值
console.log((0.1).toPrecision(20))
console.log((0.2).toPrecision(20))
console.log((0.3).toPrecision(20))
console.log(0.5 + 0.25 === 0.75) // 能用 2 的幂精确表示的小数没有误差
查看输出与解释
0.30000000000000004
false
0.10000000000000000555
0.20000000000000001110
0.29999999999999998890
true

平时打印 0.1 显示的是 0.1,是因为引擎输出时会找「能唯一还原这个浮点数的最短十进制」,不代表存的就是精确的 0.1。用 toPrecision(20) 多看几位,真相就出来了。 0.1 和 0.2 都偏大,0.3 的近似值却偏小,所以 0.1 + 0.2 的结果和 0.3 落在了两个相邻但不同的浮点数上,=== 自然是 false。反过来,0.5 和 0.25 分别是 1/2 和 1/4,二进制能精确表示,相加没有任何误差。

这不是 JavaScript 的 bug
Java 的 double、Python 的 float、C 的 double 算 0.1 + 0.2 结果都一样,因为它们用的是同一个标准。JavaScript 的特殊之处只在于它没有单独的整数类型,所有数字默认都是浮点数,所以这个问题更容易被碰到。

比较和计算的正确姿势

常见的答案是「差值小于 Number.EPSILON 就算相等」。这个说法只在 1 附近成立。浮点数越大,相邻两个值之间的间距越大,1000 附近的间距已经是 Number.EPSILON 的好几百倍,固定阈值就不够用了。

用相对误差比较,金额转整数JavaScript
function nearlyEqual(a, b) {
  // 相对误差:数值越大,允许的误差跟着变大
  const scale = Math.max(1, Math.abs(a), Math.abs(b))
  return Math.abs(a - b) <= Number.EPSILON * scale
}
console.log(nearlyEqual(0.1 + 0.2, 0.3))
// 固定用 EPSILON 当阈值,数一大就失效
console.log(Math.abs(1000.1 + 0.2 - 1000.3) < Number.EPSILON)
console.log(nearlyEqual(1000.1 + 0.2, 1000.3))
// 金额转成「分」时,乘 100 本身也会带误差
console.log(1.1 * 100)
console.log(Math.round(1.1 * 100) + Math.round(2.2 * 100)) // 单位:分
查看输出与解释
true
false
true
110.00000000000001
330

nearlyEqual 把容差乘上两数中较大的绝对值,这样不论数字大小,允许的误差都在同一个相对量级。业务里如果精度要求没那么高,比如坐标比较,直接用一个业务容差(如 1e-6)也完全可以,关键是别拿 === 比。 金额的做法是后端直接下发整数分,前端全程用整数算,只在展示时除以 100。如果拿到的是元,转换时一定要 Math.round,因为 1.1 * 100 本身就是 110.00000000000001。更复杂的场景,比如多次乘除、汇率换算,用 decimal.js 这类十进制库,或者整数部分超出安全范围时改用 BigInt。

展示时用 toFixed 要小心
(0.1 + 0.2).toFixed(2) 能得到 '0.30',用来展示没问题。但 toFixed 返回的是字符串,而且它按真实的二进制值舍入,(1.005).toFixed(2) 是 '1.00'。所以它只适合做最后一步的显示格式化,不适合拿来「修正」计算结果。
回答前,多想一步

容易答错的地方

以为所有小数都有误差
只有分母不是 2 的幂的小数才存不准。0.5、0.25、0.125 都能精确表示,整数在 Number.MAX_SAFE_INTEGER 以内也全是精确的。说「浮点数都不精确」不对,面试官会追问哪些是精确的。
用 Number.EPSILON 当万能阈值
Number.EPSILON 是 1 和下一个可表示浮点数之间的差,只描述 1 附近的精度。拿它去比较 1000 左右的数,两个其实「相等」的结果也会被判成不等,上面的代码已经演示了。
用 toFixed 或 parseFloat 修正精度
parseFloat((0.1 + 0.2).toFixed(10)) 看起来能得到 0.3,但它只是把显示结果截短了,中间每一步计算的误差还在,而且 toFixed 自己也有舍入问题。计算过程中要么用整数,要么用十进制库。
试着用自己的话回答

面试官还会怎么问?

Number.MAX_SAFE_INTEGER 为什么是 2 的 53 次方减 1?

双精度浮点数有 52 位尾数,加上隐含的一位 1,一共 53 位有效二进制位。在 2 的 53 次方以内,每个整数都能精确表示;超过之后,相邻浮点数的间距变成 2,2 ** 53 + 1 就存不下了,会被舍成 2 ** 53。

后端返回的 ID 超过 16 位,前端拿到后变了怎么办?

这是因为 JSON.parse 把它解析成了 Number,超出安全整数范围后被舍入。最稳妥的是让后端把 ID 作为字符串返回。前端没法改接口时,可以在解析前用正则把大数字段包成字符串,或者用支持大数的 JSON 解析库。

0.1 + 0.2 和 0.3 相差多少?

相差约 5.55e-17,正好是 0.3 附近两个相邻浮点数之间的一个间距。也就是说 0.1 + 0.2 的结果是 0.3 的近似值「下一个」浮点数,只差一格。

从一道题,走向一组知识

把知识连起来

值与类型

Number 能表示的最小正数是多少?为什么会有 +0 和 -0?

同样出自 IEEE 754 双精度格式,那题讲它能表示的最小正数和正负零。

值与类型

数字分隔符 _ 能用在哪些字面量里?写在哪些位置会报错?

数字分隔符只改写法不改数值,写 0.000_1 这类小数照样有精度误差。

参考资料

  • JavaScript:值与类型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 误差到底从哪来
  3. 比较和计算的正确姿势
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑