ESLint no-loss-of-precision 规则全解析:拦截 JavaScript 数字字面量的运行时精度丢失
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
本篇指南以 ESLint 内置规则no-loss-of-precision(官方规则文档)为核心,讲解它如何识别并阻止那些在编译期"看着正确、运行期却悄悄变值"的数字字面量。读完本文,你将掌握 IEEE 754 双精度浮点数的精度边界、该规则覆盖的十进制/二进制/八进制/十六进制字面量判定逻辑,以及它在eslint:recommended推荐配置下的实际接入方式。
规则背景:数字字面量为何会"静默失真"
no-loss-of-precision是一条problem类型的规则,用于禁止使用会在运行时因 64 位浮点舍入而丢失精度的数字字面量(见 docs/src/rules/no-loss-of-precision.md 的引言)。它不检查任何运行时计算,只针对源码中直接写死的数字(number literal)做静态审计。
JavaScript 中所有Number都按 IEEE 754 标准的双精度(double-precision)浮点数存储,其尾数部分仅能精确表示约 15~16 位十进制有效数字。一旦程序员在字面量中写下超出该精度的额外数字,这些数字在转换为Number类型时就会被舍入丢弃,导致程序行为与源码意图不一致——而这种错误通常不会抛出任何异常,属于最隐蔽的 bug 类型之一。
在 ESLint 的规则元数据中(见 docs/src/_data/rules_meta.json),该规则被标记为:
type: "problem":属于"确定有问题"的代码错误,而非风格建议;recommended: true:已被列入eslint:recommended推荐集,任何启用推荐配置的项目默认开启;dialects: ["JavaScript", "TypeScript"]:同时适用于 JavaScript 与 TypeScript 语法。
规则详情:精度丢失的判定原理
十进制字面量的判定流程
从源码实现看(lib/rules/no-loss-of-precision.js),规则的入口在create(context)中监听Literal节点,并通过isNumber()先排除字符串、布尔值等非数值字面量:
create(context) { return { Literal(node) { if (isNumber(node) && losesPrecision(node)) { context.report({ messageId: "noLossOfPrecision", node, }); } }, }; }核心判断函数losesPrecision按进制分流:十进制走baseTenLosesPrecision,其余走notBaseTenLosesPrecision。
十进制路径的判定思路非常精妙(lib/rules/no-loss-of-precision.js):
- 规范化源文字面量:先调用
getRaw()剔除数字分隔符_(如9_007_199_254_740_993→9007199254740993),再用convertNumberToScientificNotation()把字面量转换为"科学计数法对象"ScientificNotation,其中coefficient保存去除前导零与尾随零后的有效数字字符串,magnitude保存数量级(十进制指数)。 - 特殊情况处理:若数值为 0,则直接校验规范化后的系数是否全为 0;若字面量的有效数字超过 100 位(
requestedPrecision > 100),无需转换即可判定为精度丢失。 - 回读比对:用
node.value.toPrecision(requestedPrecision)把运行时已存储的数值按用户请求的精度重新格式化为字符串,再同样规范化;如果"用户想要的数"与"实际存储的数"在数量级或有效数字系数上不一致,即判定该字面量会丢失精度。
换句话说,规则不是凭位数一刀切(12300000000000000000000000有 26 位却合法),而是精确对比"源码想表达的数值"与"运行时真实能表示的数值",只有当两者真正不同才报告。
非十进制字面量的判定流程
对于二进制(0b/0B)、八进制(0o/0O以及旧式0前缀)、十六进制(0x/0X)字面量,notBaseTenLosesPrecision采用更直接的方法(lib/rules/no-loss-of-precision.js):将去除分隔符后的原始字符串按对应进制toString(base)与node.value的实际值做回读比较,若源码写出的进制表示与运行时真实值的进制表示不一致,说明发生了精度丢失。
isBaseTen()(lib/rules/no-loss-of-precision.js)负责识别进制:检查原始文本是否以0x/0X/0b/0B/0o/0O前缀开头,或是否符合旧式八进制形态^0[0-7]+$。
不正确的代码示例
以下代码会被no-loss-of-precision以"error"级别报告(规则消息为noLossOfPrecision,文案是"This number literal will lose precision at runtime.",见 lib/rules/no-loss-of-precision.js):
/*eslint no-loss-of-precision: "error"*/ const a = 9007199254740993 const b = 5123000000000000000000000000001 const c = 1230000000000000000000000.0 const d = .1230000000000000000000000 const e = 0X20000000000001 const f = 0X2_000000000_0001;逐个解读这些用例(均有对应测试佐证,见 tests/lib/rules/no-loss-of-precision.js):
a = 9007199254740993:这是经典的Number.MAX_SAFE_INTEGER(9007199254740991)+ 2,超过安全整数范围后相邻整数已无法区分;b = 5123000000000000000000000000001:31 位有效数字远超双精度容量,且末尾的1会被舍入吞掉;c = 1230000000000000000000000.0、d = .1230000000000000000000000:整数与小数部分均因有效位数过长而失真;e = 0X20000000000001、f = 0X2_000000000_0001:十六进制字面量同样会丢失精度,且支持带数字分隔符的写法(_需要 ES2021 及以上的语法环境)。
测试中还覆盖了更多形态的非法用例(tests/lib/rules/no-loss-of-precision.js),例如带分隔符的90_0719925_4740.9_93e3、指数形式9.007199254740993e15、9007199254740.993e3、2e999、下溢到 0 的1e-350与1e-324,以及二进制0b100000000000000000000000000000000000000000000000000001、八进制0o400000000000000001等非十进制用例。
正确的代码示例
以下写法均不会触发该规则:
/*eslint no-loss-of-precision: "error"*/ const a = 12345 const b = 123.456 const c = 123e34 const d = 12300000000000000000000000 const e = 0x1FFFFFFFFFFFFF const f = 9007199254740991 const g = 9007_1992547409_91要点说明:
a = 12345、b = 123.456:有效数字有限,双精度可精确表示;c = 123e34:虽然数值极大,但科学计数法只包含 3 位有效数字,仍可精确存储;d = 12300000000000000000000000:26 位全是"后缀零",尾数只需保存123与指数,不损失任何有效信息(这与前文b = 5123...0001结尾非零形成鲜明对比);e = 0x1FFFFFFFFFFFFF:十六进制 53 位全 1,恰好是双精度尾数能容纳的极限(对应十进制9007199254740991);f = 9007199254740991:Number.MAX_SAFE_INTEGER本身;g = 9007_1992547409_91:数字分隔符只是书写形式,不影响规则判定(getRaw()会先剔除_)。
测试用例还确认了以下边界情况均为合法(tests/lib/rules/no-loss-of-precision.js):
- 各种指数形态:
123.0e34、123e-34、-12.3e-34、9.00e2、9.0000000000e10(回归自 eslint 议题 #19957 的修复); - 极小值
5e-324(双精度最小正非零次正规数)、0.00000000000000000000000123; - 零的各种写法
0、0.0、0.、-0、0e5,以及带大量尾随零的123.0000000000000000000000; - 旧式八进制形态
019.5、0195、00195、0008、0377777777777777777; - 非数值字面量
true、'abc'、null、undefined、对象与数组字面量、'9007199254740993'(字符串不会参与数值精度判定); - 带分隔符的合法写法
12_34_56、0b111_111_...、0x1FFF_FFFF_FFF_FFF等。
配置方式:Options 与推荐集接入
该规则没有配置项
no-loss-of-precision的meta.schema为空数组(见 lib/rules/no-loss-of-precision.js),即不接收任何选项,无法通过参数放宽或收紧判定阈值。规则行为由实现内部固定:有效数字超过 100 位一律报错,其余情况严格比对用户意图与实际存储值。
单独启用该规则,可在扁平配置(flat config)中直接写入:
// eslint.config.js export default [ { rules: { "no-loss-of-precision": "error", }, }, ];或使用传统的eslintrc风格:
{ "rules": { "no-loss-of-precision": "error" } }已纳入 eslint:recommended
由于recommended: true(见 docs/src/_data/rules_meta.json),只要你的配置扩展了eslint:recommended,该规则就默认以"error"级别生效,无需显式声明。这意味着在绝大多数标准 ESLint 项目中,这类精度丢失字面量会直接在npx eslint或编辑器集成中亮起错误提示。
源码视角:规则如何做到"既精准又不误报"
深入实现可以发现几个值得称道的设计(lib/rules/no-loss-of-precision.js):
- 科学计数法抽象:
ScientificNotation类(lib/rules/no-loss-of-precision.js)用"系数 + 数量级"二元组统一表达整数与浮点,normalizeInteger/normalizeFloat(lib/rules/no-loss-of-precision.js)负责剥离前导零、尾随零并计算数量级,从而把12300000000000000000000000和123e25归一到同一表示。 - 不依赖经验阈值:除 100 位有效数字的兜底外,判定完全基于
toPrecision()回读比对,因此不会误伤"位数多但恰好可精确表示"的字面量。 - 进制全覆盖:
isBaseTen、notBaseTenLosesPrecision配合parseInt与toString(base)覆盖了二进制、八进制(新旧两式)、十六进制全部字面量形态。 - 与解析器解耦:
losesPrecision(node)只消费 ASTLiteral节点的value与raw字段,因此天然兼容 TypeScript(测试中通过@typescript-eslint/parser单独验证了 TS 场景,见 tests/lib/rules/no-loss-of-precision.js)。
规则的注册入口位于 lib/rules/index.js,采用懒加载方式() => require("./no-loss-of-precision")引入,避免在仅启用少量规则时拖慢启动。文档站点的规则元数据与版本信息(7.1.0起收录)可在 docs/src/_data/rule_versions.json 与 docs/src/_data/rules_meta.json 中查阅。
实践建议
- 大整数改用
BigInt:当业务确实需要超过Number.MAX_SAFE_INTEGER(9007199254740991)的整数时,应使用9007199254740993n这类BigInt字面量,而不是依赖会失真的普通数字字面量。 - 关注有效数字而非位数:判定标准是"有效数字",末尾补零通常安全,末尾带非零有效位则危险——
1.0000000000000000000000123这类"夹心零"同样会触发报告。 - 保持默认推荐配置:既然该规则无需选项且已进
eslint:recommended,直接沿用推荐集即可获得静态防护,无需额外维护。 - 警惕科学计数法形态:
9.007199254740993e15、2e999(溢出为Infinity边界)、1e-324与1e-350(下溢为 0)等指数写法也会被捕获,说明规则不仅处理"看着就很长"的数字。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考