目录
Math.round()
JavaScript 提供了一个函数Math.round()
来做整数的四舍五入。
1 2 3 |
console.log(Math.round(0.4), Math.round(-0.4),Math.round(0.5), Math.round(-0.5), Math.round(0.6), Math.round(-0.6)); // 预期输出: 0 -0 1 -0 1 -1 |
Math.round()
准确的定义是把参数转化为最接近的整数,如果与相邻两个整数距离相等那么返回较大的整数。在正数情况下符合日常理解的四舍五入的行为,在负数情况下更像是五舍六入。 例如, -0.5,距离相邻两个整数0, -1 距离一样,返回较大的整数 0。-0.6 距离-1更近,返回-1。
使用Math.round()先乘100再除100保留两位小数 ❌
Math.round() 只能处理整数,可以通过乘以100把小数点左移两位,然后再通过Math.round()完成整数的四舍五入,然后再把小数点右移两位(x1/100)。
1 2 3 4 5 6 |
function round2(num) { return Math.round(num * 100)/100; } console.log(round2(3.1415926), round2(3.145926)) // 预期输出: 3.14 3.15 |
看起来是好的,再多一点测试。
1 2 3 |
console.log(round2(1.005)) // 预期输出: 1.01 实际输出: 1 |
Number.EPSILON ❌
浮点计算会丢精度可以考虑加一点点数值来修正。加一个Number.EPSILON
。
Number.EPSILON
是一个常量值,最小的大于1的浮点数和1的差。
Chrome下 Number.EPSILON
=2.220446049250313e-16
1 2 3 4 5 6 |
function round2(num) { return Math.round((num+Number.EPSILON) * 100)/100; } console.log(round2(3.1415926), round2(3.145926), round2(1.005)) // 预期输出: 3.14 3.15 1.01 |
再多一点测试
1 2 3 |
console.log(round2(5.015)) // 预期输出: 5.02 实际输出: 5.01 |
没的救了,换个其他方法
toFixed ❌
toFixed 参数是小数点后保留几位小数,返回的是string。参数默认为0。
1 2 3 |
console.log(12.345.toFixed(), 12.345.toFixed(1), 12.345.toFixed(2)) // 预期输出: 12 12.3 12.35 |
1 2 3 4 5 6 |
function round2(num) { return parseFloat(num.toFixed(2)); } console.log(round2(5.015)) // 预期输出: 5.02 实际输出: 5.01 |
一模一样的失败,还有个toPrecision 跟 toFixed 一样的失败。
toPrecision ❌
toPrecision 参数一共有几位有效数字,包括整数和小数位。如果没填参数,就等同于Number.prototype.toString()
。
1 2 3 |
console.log(12.345.toPrecision(), 12.345.toPrecision(1), 12.345.toPrecision(2), 12.345.toPrecision(3), 12.345.toPrecision(4)) // 预期输出: 12.345 1e+1 12 12.3 12.35 |
toPrecision & round
浮点数在计算时会丢失精度,可以通过JavaScript有效数字位数是17, 可以通过降低精度toPrecision(15)完成精度的修正。差不多算是错错之后看起来为正。
1 2 3 4 5 6 |
function round2(num) { return Math.round((num* 100).toPrecision(15) )/100; } console.log(round2(5.015), round2(1.005), round2(39.425)) // 预期输出: 5.02 1.01 39.43 |
Exponential notation
任何一个实数都可以写成 x * 10^y 的形式,用指数表示法就是 xEy
, e
或E
在浮点数字中代表指数。
1 2 3 |
console.log(1e2==100, 1e-2=0.01) // 预期输出: true true |
通过指数表示法,避免对浮点数做计算的方式来调整小数点位置,防止精度损失放大,然后配合round完成四舍五入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function roundNumber(num, scale) { if(!("" + num).includes("e")) { return +(Math.round(num + "e+" + scale) + "e-" + scale); } else { var arr = ("" + num).split("e"); var sig = "" if(+arr[1] + scale > 0) { sig = "+"; } return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale); } } function round2(num) { return roundNumber(num, 2); } console.log(round2(5.015), round2(1.005), round2(39.425)) // 预期输出: 5.02 1.01 39.43 |
lodash
Lodash的round实现原理类似。
_.round(number, [precision = 0])
1 2 3 |
console.log(_.round(1.005, 2)) // 预期输出: 1.01 |
为什么一个简单的四舍五入并保留两位小数这么麻烦
0.1 在浮点数里并不存在
当我们声明一个小数时,比如0.1
其实它并不是0.1
它其实是0.10000000000000000555111512312578270211815834045410
。编程语言在默认打印的时候会降低精度再打印,让他看起来是0.1
。
1 2 3 4 5 6 7 8 9 10 11 12 |
console.log(0.1.toFixed(50)) // 输出: 0.10000000000000000555111512312578270211815834045410 console.log(0.2.toFixed(50)) // 输出: 0.20000000000000001110223024625156540423631668090820 console.log(0.5.toFixed(50)) // 输出: 0.50000000000000000000000000000000000000000000000000 console.log(5.015.toFixed(50)) // 输出: 5.01499999999999968025576890795491635799407958984375 |
这样就能看出为什么保留两位小数有时候好使,有时候就不好使。
浮点的标识方式
浮点数是采用科学计数法来表示一个数字的,常见的编程语言的浮点数都是基于IEEE754标准。 IEEE754 浮点数基数固定是2,所以还需要关注符号、尾数和指数三部分。
sign 符号, 表示正负号
exponent 指数,表示次方数
mantissa 尾数,表示精确度
$(-1)^s2^e1.mantissa$
由于基数是2,也就无法表达出我们常见的小数,例如: 0.1, 0.2, 0.05。对于无法表达的数只能使用近似值,也就导致 0.1 + 0.2 = 0.30000000000000004。
只要是浮点表示的小数,早晚会遇到精度问题。
对于需要精准小数计算的还是要用Math.js、decimal.js这种专门的库来计算。
更多浮点相关的知识
15 张图带你深入理解浮点数
IEEE-754浮点数对应二进制转化
IEEE-754浮点数可视化
IEEE-754浮点数可视化float-toy
评论关闭。