JavaScript--运算确保精度--自定义toFixed方法,乘除法计算方法优化

首先是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

JavaScript--运算确保精度--自定义toFixed方法,乘除法计算方法优化_第1张图片

也就是除法尽量少,使用运算法则改变运算顺序,但感觉还是有点担心,有小数的情况呢?所以还是使用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);

 

你可能感兴趣的:(JavaScript面试问题,算法,typescript)