JavaScript 64位浮点数运算:为何除以1/step比乘step精度更高
运算结果差异核心成因
你观察到的精度差异本质是两次浮点舍入的误差方向恰好抵消,我们直接以你举的step=0.1、v=19.94场景为例拆解计算过程:
- 所有写在代码里的十进制小数,运行时会首先被转换为最接近的64位双精度浮点数:你写的
0.1实际存储值略大于真实十进制0.1,约为0.10000000000000000555;对应的1/step即1除以这个略大的0.1,得到的实际存储值略小于10,约为9.999999999999998223。 - 两种写法的第一步计算完全一致:
v/step后经Math.round得到整数计数值199,差异完全出在最后一步还原步长的运算上:- 第一种写法
199 * step:用整数199乘略大于0.1的step存储值,计算结果约为19.90000000000000209,这个值离双精度浮点数可表示的19.9对应值有微小距离,硬件就近舍入后就得到了你看到的带误差的19.900000000000002。 - 第二种写法
199 / (1/step):用整数199除以略小于10的1/step存储值,计算结果刚好落在双精度浮点数中对应19.9的精确可表示值的舍入范围内,硬件就近舍入后直接返回19.9对应的标准浮点值,肉眼看不到误差尾巴。
- 第一种写法
对该现象的补充说明
- 这个结果对熟悉IEEE754浮点规则的开发者是完全符合直觉的:每一次浮点乘除运算都会做一次就近舍入,引入方向、大小随机的极小误差,当连续两次运算的舍入误差大小相近、方向相反时,就会出现误差抵消得到更精确结果的情况。
- 你测试时遇到的“全场景更准”不是通用规律,只是测试用例恰好选了步长为10的负整数次幂(0.1、0.01、0.001、0.0001)的场景:这类步长的存储值固定略大于真实十进制值,
1/step的存储值固定略小于对应的10的整数次幂,乘法引入的正误差和除法引入的负误差稳定抵消,才会出现第二种写法精度更高的现象。如果换步长为0.2、0.3、0.7这类非10负幂的值,会随机出现某一种写法精度更高的情况,没有必然的优劣。 - 实际工程中不会靠这种误差抵消的写法碰运气:处理步长舍入、金额计算这类对十进制精度有要求的场景,通用方案是先把所有值放大对应倍数转为整数做运算,全部计算完成后再缩放回对应量级,从根源上避免中间过程的浮点舍入误差;精度要求极高的场景会直接使用十进制精确运算库处理。
附测试代码
function test(start, end, inc, step) { const addRow = makeTable(step, 'div', 'mult'); for (let v = start; v <= end; v += inc) { const steppedVMult = Math.round(v / step) * step; const steppedVDiv = Math.round(v / step) / (1 / step); addRow(v, steppedVDiv, steppedVMult); } } test(0, 2, 0.03, 0.1); test(0, 0.2, 0.003, 0.01); test(0, 0.02, 0.0003, 0.001); test(0, 0.002, 0.00003, 0.0001); function c(tag, children = [], text = '') { const e = document.createElement(tag); e.textContent = text; children.forEach(c => e.appendChild(c)); return e; } function makeTable(...args) { const makeRow = arr => c('tr', arr.map(v => c('td', [], v))); const tbody = c('tbody'); const table = c('table', [ c('thead', [makeRow(args)]), tbody, ]); document.body.appendChild(table); return function(...args) { tbody.appendChild(makeRow(args)); }; }
body { font-family: monospace; } table { border-collapse: collapse; margin-bottom: 10px } thead { font-weight: bold; } td { border: 1px solid #888; padding: 3px; }
内容的提问来源于stack exchange,提问作者samanthaj
相关产品推荐
相关产品推荐

