0.1+0.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
10.22*100 = 1022.0000000000001
2.4/0.8 = 2.9999999999999996
32.2*100 = 3220.0000000000005
(32.2*100 + 3.14*100) / 100 = 35.34 (很奇怪!!!不该是很多位小数吗???)
32.2*1000 = 32200.000000000004
32.3*100 = 3229.9999999999995
32.3*1000 = 32299.999999999996
……
js采用64位浮点数表示法(几乎所有现代编程语言所采用),这是一种二进制表示法。二进制浮点数表示法并不能精确表示类似0.1这样简单的数字。
这个问题不只在js中才会出现,在任何使用二进制浮点数的编程语言中都会出现。
JavaScript的未来版本或许会支持十进制数字类型以避免精度丢失的问题。
decimal.js
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round
/**
* Decimal adjustment of a number.
*
* @param {String} type The type of adjustment.
* @param {Number} value The number.
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* @returns {Number} The adjusted value.
*/
export default {
round: (value, exp) => {
return decimalAdjust('round', value, exp);
},
floor: (value, exp) => {
return decimalAdjust('floor', value, exp);
},
ceil: (value, exp) => {
return decimalAdjust('ceil', value, exp);
}
};
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? +value[1] - exp : -exp)));
// Shift back
value = value.toString().split('e');
value = +(value[0] + 'e' + (value[1] ? +value[1] + exp : exp));
return value;
}
js 浮点数乘以100或除以100等10的倍数容易造成精度缺失,但用 e2 这种形式来乘以或除以100等10的倍数不会造成精度缺失,如上面说的 10.22*100 = 1022.0000000000001,但用 10.22e2 这种表示就是 1022,不会造成精度缺失,decimal 就是利用了此原理来处理 js 浮点数的精度问题。
(6.265).toFixed(2) 结果为 “6.26”,正确的结果应该是 “6.27”。JS 的 toFixed 方法四舍五入时,有时候精度会丢失。
decimal.js 即解决了 js 浮点数计算后精度丢失的问题,又解决了 js toFixed 方法造成的精度丢失问题,但跟 toFixed 的还是有所区别的,toFixed 是固定保留几位小数,decimal 则是会把小数末尾的0省略掉。如果也要固定保留小数位数,可以先用 decimal 精确到几位小数,然后再用 toFixed 让其固定保留几位小数。
使用方法:
Decimal.round(各种运算, 要精确的小数位数)
各种运算的最终结果精度可能是丢失的,但用 decimal 对最终运算结果处理一下精度就不丢失了。
要精确的小数位数可选值:不传,0,负数,正数。
不传或传0 是四舍五入到整数。正数如果传1就是个位为0,十位是个位四舍五入的值。
测试 decimal.js:
import Decimal from './decimal';
console.log(Decimal.round(3.17)) //3
console.log(Decimal.round(3.17, 0)) //3
console.log(Decimal.round(1.13265, -3)) //1.133
console.log(Decimal.round(3.17, -3)) //3.17
console.log(Decimal.round(1000, -3)) //1000
console.log(Decimal.round(0.1+0.2, -3)) //0.3
console.log(Decimal.round(31216, 1)) //31220
console.log(Decimal.round(31213, 2)) //31200
Decimal.round(value/100*1000+32.2, -4)
思路:把浮点数转换成整数运算。
加法运算
export const accAdd = (arg1,arg2)=>{
let r1,r2,m;
try{r1=arg1.toString().split('.')[1].length}catch(e){r1=0}
try{r2=arg2.toString().split('.')[1].length}catch(e){r2=0}
m=Math.pow(10,Math.max(r1,r2))
return (arg1*m+arg2*m)/m;
}
Number.prototype.add = function (arg){
return accAdd(arg,this);
}
减法运算
export const accSub = (arg1,arg2)=>{
let r1,r2,m,n;
try{r1=arg1.toString().split('.')[1].length}catch(e){r1=0}
try{r2=arg2.toString().split('.')[1].length}catch(e){r2=0}
m=Math.pow(10,Math.max(r1,r2));
//动态控制精度长度
n=(r1>=r2)?r1:r2;
return (arg1*m-arg2*m)/m;
}
Number.prototype.sub = function (arg){
return accSub(arg,this);
}
乘法运算
export const accMul=(arg1,arg2)=>{
let m=0, s1='', s2='';
if(arg1&&arg1!=null)
s1=arg1.toString();
if(arg2&&arg2!=null)
s2=arg2.toString();
try{m+=s1.split('.')[1].length}catch(e){}
try{m+=s2.split('.')[1].length}catch(e){}
return Number(s1.replace('.',''))*Number(s2.replace('.',''))/Math.pow(10,m);
}
//给Number类型增加一个mul方法,调用起来更加方便,以下类似
Number.prototype.mul = function (arg){
return accMul(arg,this);
}
例: (3).mul(5) // 15
除法运算
export const accDiv = (arg1,arg2) => {
let t1 = 0, t2 = 0, r1, r2;
try {
t1 = arg1.toString().split('.')[1].length;
} catch (e) {}
try {
t2 = arg2.toString().split('.')[1].length;
} catch (e) {}
r1 = Number(arg1.toString().replace('.', ''));
r2 = Number(arg2.toString().replace('.', ''));
return (r1 / r2) * Math.pow(10, t2 - t1);
}
Number.prototype.div = function (arg){
return accDiv(arg,this);
}
除法有时候会用到本身就除不尽的情况,就要用 decimal 精确到几位小数一下。
function epsEqu(x,y) {
return Math.abs(x - y) < Math.pow(2, -52);
// 因为 Number.EPSILON === Math.pow(2, -52),所以也可以这么写:
// return Math.abs(x - y) < Number.EPSILON;
}
// 举例
0.1 + 0.2 === 0.3 // false
epsEqu(0.1 + 0.2, 0.3) // true
小数比较时,要给它一个误差范围,在误差范围内的都算相等。
parseInt(0.0000008) // -> 8