这个问题你可能在面试、线上 Bug、甚至随手写 Demo 的时候都见过1console.log(0.1 0.2 0.3);// false很多人第一反应是“浮点数精度问题”但如果继续追问为什么偏偏是0.1、0.2这种小数出问题“精度”到底精在哪一位、丢在哪一步实际开发里应该怎么比较、怎么计算才稳这篇文章按“现象 → 原因 → 解决 → 面试回答”的顺序把它讲透。先看现象1234console.log(0.1 0.2);// 0.30000000000000004console.log(0.1 0.7);// 0.7999999999999999console.log(0.3 - 0.2);// 0.09999999999999998console.log(0.1 * 3);// 0.30000000000000004但有些计算又是对的123console.log(0.5 0.5);// 1console.log(0.25 0.25);// 0.5console.log(0.125 * 8);// 1为什么根本原因二进制无法精确表示某些十进制小数计算机用二进制存储数字。十进制的 0.5 在二进制里是 0.1能精确表示。但十进制的 0.1 在二进制里是10.0001100110011001100110011001100110011... (无限循环)就像十进制里 1/3 0.333... 无限循环一样0.1 在二进制里也是无限循环的。但计算机内存有限不能存无限长的数字必须在某个位置截断。JavaScript 用的是 IEEE 754 双精度浮点数只有 64 位其中 52 位用来存小数部分。截断就意味着误差。哪些数能精确表示能被 2 的幂次整除的小数在二进制里都能精确表示12345// 这些都是精确的0.5 1/2 0.1 (二进制)0.25 1/4 0.01 (二进制)0.125 1/8 0.001 (二进制)0.0625 1/16 0.0001 (二进制)而 0.1 1/1010 2 × 5有因子 5所以在二进制里是无限循环。规律分母只包含因子 2 的分数在二进制里能精确表示。怎么解决方案一容差比较推荐既然有误差那就别用比较改用误差小于某个值12345function isEqual(a, b) {returnMath.abs(a - b) Number.EPSILON;}console.log(isEqual(0.1 0.2, 0.3));// trueNumber.EPSILON是 JavaScript 里最小的可表示精度差约等于 2.22e-16。方案二转成整数算小数不精确整数是精确的。把小数转成整数算完再转回来1234567891011121314// 0.1 0.2constresult (0.1 * 10 0.2 * 10) / 10;// 0.3// 封装一下function add(a, b) {constprecision Math.max((a.toString().split(.)[1] ||).length,(b.toString().split(.)[1] ||).length);constfactor Math.pow(10, precision);return(Math.round(a * factor) Math.round(b * factor)) / factor;}console.log(add(0.1, 0.2));// 0.3方案三toFixed 四舍五入简单粗暴直接四舍五入12constresult parseFloat((0.1 0.2).toFixed(10));console.log(result);// 0.3注意toFixed返回的是字符串要用parseFloat转回数字。方案四用专门的库金融计算这种对精度要求高的场景用专门的库123456// decimal.jsimport Decimalfromdecimal.js;consta newDecimal(0.1);constb newDecimal(0.2);console.log(a.plus(b).toString());// 0.3实际开发中怎么选面试怎么答JavaScript 用 IEEE 754 双精度浮点数存储数字。十进制的 0.1 在二进制里是无限循环小数但只有 52 位存储空间必须截断所以有精度损失。0.1 和 0.2 存储时都有微小误差加起来误差累积结果就不等于 0.3 了。解决方案有几种比较时用容差比较、计算时转成整数、或者用 decimal.js 这样的高精度库。