首先是toFixed方法,原本的方法有一些误差,小数点部分不是真正的四舍五入
例如console.log(1.335.toFixed(2)) //1.33
所以有必要重写一下:
const toFixed = function (myNum,s) {
// Math.pow(10, s) =>10的s次方
// Math.round=>整数后面的第一个小数数字四舍五入=>例如0.0055501*10^2=>0.5xxx=>1
// 1/100=>0.01
myNum = (Math.round(myNum * Math.pow(10, s)) / Math.pow(10, s)).toString();
// 匹配'.'的字符串的下标
let index = myNum.indexOf(".");
// 当'.'不存在且保留小数位数大于0时=>myNum是整数时,直接加'.',再按照小数位数用0占位
if (index === -1 && s > 0) {
myNum += ".";
for (let i = 0; i < s; i++) {
myNum += "0";
}
} else {
// '.'之后的第一个字符所在的下标
index = myNum.length - 1 - index;
// s - index若大于等于1就加'0'
for (let i = 0; i < (s - index); i++) {
myNum = myNum + "0";
}
}
return myNum;
}
let num = '0.0055501'
console.log(toFixed(parseFloat(num),2))//0.01
console.log(toFixed(parseFloat(num),3))//0.006
console.log(toFixed(parseFloat(num),8))//0.00555010
console.log(toFixed(parseFloat(0.005 * 0.22),2))//0.00
之所以传参的时候还要使用parseFloat,因为JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算!!
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
注意:
1.使用Math.round方法将浮点数四舍五入变成整数的特点切题
2.Math.round(xx浮点数*(10^s)/(10^s)) 得到四舍五入的数字(可能是整数,也可能是浮点数)
3.然后按照要求保留s位小数位数,不足位数的补0
但是,事实的情况是JavaScript的加减乘除有精度缺失的问题,这里主要讲除法计算方法的优化:
// 针对除不尽的除数做处理--主题思想: 先乘以10^n,这样除以除数的时候数字变大了,精度就提高了,然后再除以10^n拿到原本的结果
export const divided = (myNum: number|string, dividedNum:number = 1) => {
const num1 = String(myNum).replace('.','');
const num2 = String(dividedNum).replace('.','');
const len1 = String(myNum).split('.')[1].length;
const len2 = String(dividedNum).split('.')[1].length;
myNum = ((Number(num1) / Number(num2)))
*Math.pow(10, len2 - len1);
return isNaN(Number(myNum)) ? 0: Number(myNum);
}
这样得到尽量精确的原始数字,再使用上述的toFixed函数结果就尽可能精确了.
eg:
使用控制台: 3824.205/11/11 => 使用toFixed(3824.205/11/11, 2) => 31.60
而若是3824.205/121 =>保留2位小数却是31.61
也就是除法尽量少,使用运算法则改变运算顺序,但感觉还是有点担心,有小数的情况呢?所以还是使用divided函数先做一下数字的处理:
使用toFixed(divided(3824.205, 11*11), 2) => 31.61
然后使用手机计算机比对计算结果(注意四舍五入)
更为精确
至于乘法: 主要考虑小数的乘法
逻辑: 先把每个乘数*10^n变为整数=>s1.replace('.', '')比较高效,其他方法不赘述 =>这样自然都是整数相乘,再除以10^n得到尽量精确的结果:
// 乘法函数
export const accMul = (arg1:number, arg2:number, arg3:number = 1) => {
let m = 0;
let s1 = String(arg1);
let s2 = String(arg2);
let s3 = String(arg3);
try {
m += s1.split('.')[1].length;
} catch(e) {}
try {
m += s2.split('.')[1].length;
} catch(e) {}
try {
m += s3.split('.')[1].length;
} catch(e) {}
return Number(s1.replace('.', ''))
* Number(s2.replace('.', ''))
* Number(s3.replace('.', ''))
/ Math.pow(10, m);
}
虽然以上方法能够解决大多数问题,但是也会有特殊情况:
使用浏览器控制台输入Math.round(69.945*Math.pow(10,2))/Math.pow(10,2)
结果居然是69.94!!!
使用浏览器控制台输入Math.round(79.945*Math.pow(10,2))/Math.pow(10,2)
结果居然是79.94!!!
完全不是四舍五入的结果,这样看来上面的方法还是有局限性的
所以还是更细致地判断比较好:
// 注意:此为直接截取小数点后s位的方法,非四舍五入--数字字符串--'2.00'
export function toFixedFloor(myNum: number | string, s: number) {
myNum = Number(myNum);
if(isNaN(myNum)) return `0.${'0'.repeat(s)}`;
myNum = (
Math.floor(myNum * Math.pow(10, s)) /
Math.pow(10, s)).toString();
return addZeroStr(myNum, s);
}
// 保留小数位数的数字不足的加0补足
export const addZeroStr = (myNum: number | string, s: number) => {
myNum = String(getNum(myNum));
// 匹配。字符串下标
let index = myNum.indexOf('.');
if (index === -1 && s > 0) {
myNum += `.${'0'.repeat(s)}`;
} else {
index = myNum.length - 1 - index;
myNum += `${'0'.repeat(s - index)}`;
}
return myNum;
}
// 四舍五入--数字字符串--'2.05'
export function toFixedRound(myNum: number | string, s: number) {
const myNewNum:string = toFixedFloor(myNum, s+1);
// console.log(myNum, '四舍五入!!!!!!', 'hhhhhhhhhhhhhhhhhhhhhh')
// 小数四舍五入
myNum = String(getNum(myNewNum));
const numArr:any[] = myNum.split('.');
// 保留x位小数,从x+1位小数是否大于等于5判断
const lastIdx:number = myNum.length-1;
const num1:string = myNum.substring(0, lastIdx);
if(numArr.length>1&&numArr[1].length>s) {
if(Number(myNum[lastIdx])>=5) {
const num:number = Number(num1)*Math.pow(10,num1.split('.')[1].length) + 1;
const newNum:string = String(num / Math.pow(10,num1.split('.')[1].length));
myNum = newNum.substring(0, newNum.length);
}
else if(Number(myNum[lastIdx])<5) {
myNum = myNum.substring(0, lastIdx);
}
return myNum;
}
else {
return myNewNum.substring(0, myNewNum.length-1);
}
};
export const getNum = (myNum:any) => isNaN(Number(myNum)) ? 0: Number(myNum);