Q&A
Q:前端四舍五入并且保留两位小数的时候,应该怎么做?
A:Number.toFixed(2),就好了
平时在开发中,我们前端部分都是在用toFixed方法进行四舍五入并保留两位小数的,这种方式看上好像四舍五入是没有问题的,但一些特殊情况,还是有一些偏差。
计算机的运算一般都是不精确的,举个栗子:
这就涉及到了几个方面的问题:
存储(可能不精确)
我们把 0.2 变成二进制看一下
变成了0011 一直循环,
但计算机的存储能力有限,当到了一定的长度后,后面的数字就不要了,当然,舍去的时候,也不是直接丢弃的舍去,而是看要保留位数的后一位是什么,再举个栗子:
0.00110011 保留六位时,后一位是1(入),所以得到 0.001101
0.00110011 保留5位时,后一位是0(舍),所以得到 0.00110
所以我们可以看到上面的 0.2 转换成二进制后,是以 01 结尾的,而不是0011这种
然后我们把 0.2 的十进制多保留几位小数,,这个时候就发现 0.2 的 存储 其实就是 不精确 的
这样就会造成一个情况出现:这两个值的再计算机里存储的内容是完全一样的
那也就意味着这两个数字是完全相等的
当然也不是所有的数字存储都是不精确的,
运算(可能不精确)
因为运算就是用的存储的二进制进行运算的,但是结果显示的时候,是用十进制来显示的
不精确的
又精确了??
计算机在运算的过程中,可能会存在一些抵消,有些不精确的存储可能会存的大一写,有些不精确的存储可能会存的小一点,这样在计算的时候一抵消,结果可能就变得精确了,举个栗子:
0.2的最后进行了入的操作,0.3的最后进行的舍的操作,一入一舍,刚好抵消
所以运算的过程中,是拿存储的二进制进行运算的
显示
这里有一个疑问:为什么这个地方的0.2又是正确的?
因为计算在显示的时候会做一个近似处理,0.200000000000001,就会显示 0.2
网上也有人说是 银行家算法
toFixed它是一个四舍六入五成双的诡异的方法(也叫银行家算法)
"四舍六入五成双"含义:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”
也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:①,5前为奇数,舍5入1;②,5前为偶数,舍5不进。(0是偶数)
然后在说回 toFixed ( ) 方法,这个方法是先运算再显示
2.45 保留一位小数 = 2.5
2.55 保留一位小数 = 2.5
接下来就是一些解决 js 精度问题的方法:
1,自己写方法
numberCal = { // 加法运算 add(a, b) { let c, d, e; try { c = a.toString().split( "." )[1].length; } catch (f) { c = 0; } try { d = b.toString().split( "." )[1].length; } catch (f) { d = 0; } return ( (e = Math.pow(10, Math.max(c, d))), ( this .mul(a, e) + this .mul(b, e)) / e ); }, // 减法运算 sub(a, b) { let c, d, e; try { c = a.toString().split( "." )[1].length; } catch (f) { c = 0; } try { d = b.toString().split( "." )[1].length; } catch (f) { d = 0; } return ( (e = Math.pow(10, Math.max(c, d))), ( this .mul(a, e) - this .mul(b, e)) / e ); }, // 乘法运算 mul(a, b) { let c = 0, d = a.toString(), e = b.toString(); try { c += d.split( "." )[1].length; } catch (f) { // console.log(`ERROR!!!!(╯°Д°)╯︵ ┻━┻`, f); } try { c += e.split( "." )[1].length; } catch (f) { // console.log(`ERROR!!!!(╯°Д°)╯︵ ┻━┻`, f); } return ( (Number(d.replace( "." , "" )) * Number(e.replace( "." , "" ))) / Math.pow(10, c) ); }, // 除法运算 div(a, b) { let c, d, e = 0, f = 0; try { e = a.toString().split( "." )[1].length; } catch (g) { console.log(`ERROR!!!!(╯°Д°)╯︵ ┻━┻`, g); } try { f = b.toString().split( "." )[1].length; } catch (g) { console.log(`ERROR!!!!(╯°Д°)╯︵ ┻━┻`, g); } return ( (c = Number(a.toString().replace( "." , "" ))), (d = Number(b.toString().replace( "." , "" ))), this .mul(c / d, Math.pow(10, f - e)) ); }, }; |
使用
import { numberCal } from "./utils/index" ; let a = 0.1; let b = 0.2; const num = numberCal.add(a, b); console.log(num); // 0.3 |
2,Decimal.js
github地址:GitHub - MikeMcl/decimal.js: An arbitrary-precision Decimal type for JavaScript
中文API:decimal.js 中文API
npm install --save decimal.js // 安装 import Decimal from "decimal.js" // 具体文件中引入 |
使用
import Decimal from "decimal.js" ; const a = 0.1; const b = 0.2; // a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容 // 下面两种都可以 可以带 new 也不可以不带 new const res1 = new Decimal(a).add( new Decimal(b)); const res2 = Decimal(a).add(Decimal(b)); console.log(res1.toNumber(), res2.toNumber()); // 0.3 0.3 |
3,Math.js
链接:math.js | an extensive math library for JavaScript and Node.js
npm install --save math.js // 安装 |
import * as math from "mathjs" ; // 保留一位小数 console.log(math.round(2.45, 1)); // 2.5 // 保留一位小数 console.log(math.round(2.55, 1)); // 2.6 // 0.1 + 0.2 console.log(math.add(math.bignumber(0.1), math.bignumber(0.2)).toString()); // 0.3 |
4,big.js
链接:https://mikemcl.github.io/big.js/#
npm install --save big.js // 安装 |
import Big from "big.js" ; const number1 = Big(0.1); const number2 = Big(0.2); const number3 = number1.plus(number2).toString(); console.log(number3, "number3" ); // 0.3 |
5,*100 / 100
// 解决精度问题 console.log((0.1 * 10 + 0.2 * 10) / 10); // 0.3 |
建议
当涉及金额计算时,慎用 toFixed ( ) 方法