JavaScript小数四舍五入异常:不同方法结果不一致求解决方案
浮点数四舍五入到两位小数的不一致问题
我知道这个问题有人提过,试了各种方法都没解决,实际情况比想的复杂。现在需要处理折扣后的产品价格,必须把数值四舍五入到两位小数,但用toFixed和自己写的Math.round方法时,有些本该向上取整的数却出现了向下取整的情况。
示例代码
let numbers = [] numbers.push(1.782); numbers.push(4.705); numbers.push(4.715); numbers.push(4.725); numbers.push(4.735); numbers.push(4.745); numbers.push(4.755); numbers.push(4.765); numbers.push(4.775); numbers.push(4.785); numbers.push(4.795); for (let i=0; i<numbers.length; i++){ console.log('TEST 1 - num#'+i+ ' ['+numbers[i]+'] toFixed = '+ (numbers[i]).toFixed(2) ) } function roundToDecimal(num) { return Math.round(num * 100) / 100 } for (let i=0; i<numbers.length; i++){ console.log('TEST 2 - num#'+i+ ' ['+numbers[i]+'] roundToDecimal = '+ roundToDecimal(numbers[i]) ) }
运行结果
TEST 1 - num#0 [1.782] toFixed = 1.78 TEST 1 - num#1 [4.705] toFixed = 4.71 TEST 1 - num#2 [4.715] toFixed = 4.71 // wrong TEST 1 - num#3 [4.725] toFixed = 4.72 // wrong TEST 1 - num#4 [4.735] toFixed = 4.74 TEST 1 - num#5 [4.745] toFixed = 4.75 TEST 1 - num#6 [4.755] toFixed = 4.75 // wrong TEST 1 - num#7 [4.765] toFixed = 4.76 // wrong TEST 1 - num#8 [4.775] toFixed = 4.78 TEST 1 - num#9 [4.785] toFixed = 4.79 TEST 1 - num#10 [4.795] toFixed = 4.79 TEST 2 num#0 [1.782] roundToDecimal = 1.78 TEST 2 num#1 [4.705] roundToDecimal = 4.71 TEST 2 num#2 [4.715] roundToDecimal = 4.72 TEST 2 num#3 [4.725] roundToDecimal = 4.72 // wrong TEST 2 num#4 [4.735] roundToDecimal = 4.74 TEST 2 num#5 [4.745] roundToDecimal = 4.75 TEST 2 num#6 [4.755] roundToDecimal = 4.76 TEST 2 num#7 [4.765] roundToDecimal = 4.76 // wrong TEST 2 num#8 [4.775] roundToDecimal = 4.78 TEST 2 num#9 [4.785] roundToDecimal = 4.79 TEST 2 num#10 [4.795] roundToDecimal = 4.8
我明白这是浮点数表示的问题,但这种不一致性实在让人头疼,有没有可行的解决方案?
可行解决方案
方法1:添加微小偏移量修正精度误差
浮点数的二进制存储会导致类似4.715这样的数实际略小于理论值,进而让Math.round判断为向下取整。可以在计算前添加一个极小的偏移量来修正这个误差:
function roundToFixed(num, decimals = 2) { const offset = 10 ** (-decimals - 4); // 极小偏移,避免精度问题 return Math.round((num + offset) * (10 ** decimals)) / (10 ** decimals); }
测试验证:
const numbers = [1.782,4.705,4.715,4.725,4.735,4.745,4.755,4.765,4.775,4.785,4.795]; numbers.forEach((num, i) => { console.log(`num#${i} [${num}] roundToFixed = ${roundToFixed(num).toFixed(2)}`); });
运行后所有需要向上取整的数值都会正确处理。
方法2:基于字符串的精确四舍五入
如果要完全规避浮点数精度问题,可以把数字转为字符串,直接操作小数点后位数来判断进位:
function preciseRound(num, decimals = 2) { const str = num.toString(); const dotIndex = str.indexOf('.'); // 无小数或小数位数不足时直接返回格式化值 if (dotIndex === -1 || str.length <= dotIndex + decimals) { return Number(num.toFixed(decimals)); } // 获取小数点后第decimals+1位数字 const nextDigit = parseInt(str[dotIndex + decimals + 1], 10); // 截取到小数点后decimals位作为基础值 let base = str.slice(0, dotIndex + decimals + 1); if (nextDigit >= 5) { // 进位处理,对应小数位加1 base = (Number(base) + Math.pow(10, -decimals)).toFixed(decimals); } return Number(base); }
这个方法完全通过字符串操作处理,适合金额这类对精度要求极高的场景。
方法3:用Intl.NumberFormat格式化展示
如果只是用于页面展示,直接使用浏览器内置的格式化工具即可,它会自动处理精度问题:
const formatter = new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingMode: 'halfUp' // 标准四舍五入规则 }); numbers.forEach((num, i) => { console.log(`num#${i} [${num}] formatted = ${formatter.format(num)}`); });
注意roundingMode是ES2020新增特性,需要确认运行环境支持。
内容的提问来源于stack exchange,提问作者MistaJase
相关产品推荐
相关产品推荐

