toFixed() 踩坑----四舍六入 银行家算法

你绝对想不到 toFixed()明明是数字的方法,结果转出来的是字符串,转字符串也就算了,结果,值还有可能不对。
我们正常理解的四舍五入,及时见5就入。但是你看看
在这里插入图片描述
在这里插入图片描述
对比之下发现 不管是 数字类型的 1.45 还是1.35 在toFixed()后都是1.4,结果还是字符串。什么原因呢

其实这也不错,为啥----

银行家算法:四舍六入五考虑,五后非0 就进一,五后为0看奇偶,五前为偶当舍去,五后为奇要进一。

银行家算法是一种最有代表性的 避免死锁的算法。 在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。

例子如下。
在这里插入图片描述
在这里插入图片描述

所以这就造成了1.35 和1.45 在四舍五入后都是1.4 (银行家就是 5前奇进偶不进)

但是你看看toFixed(),假设总共有3块钱,我们用toFixed() 去分钱,结果就有问题。1.25和1.75 的时候就会多1毛前,如果在财务中,这1毛钱就翻天了。
在这里插入图片描述

好了继续说一下toFixed()(全TM是字符串),
toFixed它是一个四舍六入 五成双的诡异的方法(也叫银行家算法),
"四舍六入五成双"含义:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:①5前为奇数,舍5入1;②5前为偶数,舍5不进。(0是偶数)
但是
经过我的测试发现,在chorme下面(最新版),并没有完全遵守这个规则,尤其是5的后面没有数字的时候,不是这么判断的,如下:
继续先看例子 跟上边的测试例子有冲突

var b = 1.335
console.log(b.toFixed(2)) //	"1.33" 是不是以为是1.34

var b = 1.345
console.log(b.toFixed(2)) // 	"1.34" 是不是以为是1.35

var b = 1.355
console.log(b.toFixed(2)) //	"1.35" 是不是以为是1.36

var b = 1.365
console.log(b.toFixed(2)) //	"1.36" 是不是以为是1.37

var b = 1.375
console.log(b.toFixed(2)) // 	"1.38"

var b = 1.385
console.log(b.toFixed(2)) //	"1.39"

在这里插入图片描述
可以发现在chorme下没有完全去遵循这个规律,或许它有自己的算法,但是毕竟它没有遵循通用的银行家算法,所以

tofixed这个方法在涉及到金钱计算的业务中还是少用, 最好别用,否则可能会出大问题!

自己的做法就是根据 精确位数 来取小数点后的数,然后判断精确位是大于4还是小于等于4,上代码吧,不说了:

假设做做到 是两位小数,最少就是取整,不留小数

function moneySwitch(money, precision){ // precision是需要精确的位数,如百分位就是2
var result = 0;
//先进行一个千分位的四舍五入,保证3.0999这种情况在保留一位小数的时候能是对的,这一位可以这么做没什么问题
var money = parseFloat(money).toFixed(3);
try{
    var int_part = money.split(".")[0], // 小数点前的整数
    point_num = money.split(".")[1], // 取小数点后面的小数
    precision_num = point_num[3-precision];
    if(precision_num>4){//五入的情况
        if(precision==1){
            point_num = parseInt(point_num)+10+"";
            if(point_num.length>3){//说明往整数位进1
                int_part = parseInt(int_part)+1+"";
                point_num = point_num[1]+point_num[2];
            }else{
                point_num = point_num[0]+point_num[1];
            }
            result = parseFloat(int_part+"."+point_num);
        }else if(precision==2){
            point_num = parseInt(point_num)+100+"";
            if(point_num.length>3){//说明往整数位进1
                int_part = parseInt(int_part)+1+"";
                point_num = point_num[1];
            }else{
                point_num = point_num[0];
            }
            result = parseFloat(int_part+"."+point_num);
        }else if(precision==3){
            int_part = parseInt(int_part)+1+"";
            point_num = 0;
        }
        result = parseFloat(int_part+"."+point_num);
    }else{//四舍的情况
        if(precision==1){
            point_num = point_num[0]+point_num[1];
        }else if(precision==2){
            point_num = point_num[0];
        }else if(precision==3){
            point_num = 0;
        }
        result = parseFloat(int_part+"."+point_num);
    } 
}
catch(e){
    return parseFloat(money).toFixed(2);//如果过程中有出错就tofixed代替为解决
}
	return result;
}

真心用了toFixed()后坑死我了。

把toFixed() 转换成数字类型的方法

在这里插入图片描述
但是这些后期也会有问题。

推荐写法 Math.round(x) 函数返回一个数字四舍五入后最接近的整数。
Math.round( x * 100) / 100
在这里插入图片描述
但是发现这个只能是两位小数。所以原生的Math.round()方法也不能满足我们的需求,要知道的是 Math.round()是JavaScript数学库中的函数,用于将给定数字浮点数舍入到最接近的整数值。
它的工作方式是,如果小数部分小于0.5,则四舍五入(最接近的整数小于它),如果小数部分大于0.5,则四舍五入(最近的整数大于它)。

Syntax:

句法:

    Math.round(value);

所以我们一般会去封装一下,,各个公司都会针对数据四舍五入,时间进行封装。下边是我的封装
react项目
在这里插入图片描述
这里的rounds 接收两个参数,一个是计算的方式val,一个是保留的小数位

import { Fraction,	  BigNumber,	  MathArray,	  Matrix,	  add,	  subtract,	  multiply,	  divide,	  bignumber,	round	} from 'mathjs';
import { compose, curry, fromPairs, keys } from 'ramda';

// 数学计算工具类 - 处理高精度计算或者数字转化,提供数学算法。
type MType =
  | number
  | string
  | Fraction
  | BigNumber
  | MathArray
  | Matrix
  | boolean
  | Fraction
  | null;

// 高精度计算 - 内部统一返回BigDecimal,再统一转成number类型。
const basicHpOperations = ((maths) =>
  fromPairs(
    keys(maths).map((operation) => [
      operation,
      curry(compose(parseFloat, (bn) => bn.toString(), maths[operation]))
    ])
  ))({
  // 高精度加法
  add: (sbj: MType, acc: MType) => {
    return add(bignumber(sbj), bignumber(acc));
  },
  // 高精度减法
  sub: (sbj: MType, acc: MType) => {
    return subtract(bignumber(sbj), bignumber(acc));
  },
  // 高精度乘法
  mul: (sbj: MType, acc: MType) => {
    return multiply(bignumber(sbj), bignumber(acc));
  },
  // 高精度除法
  div: (sbj: MType, acc: MType) => {
    return divide(bignumber(sbj), bignumber(acc));
  }
});
// 将内部计算解构成4种计算 - 注意 为了防止精度丢失,全部为string类型返回
const { add: hpAdd, sub, mul, div } = basicHpOperations;
// 包裹一层,给个初值
const wrapperAdd = (sbj = 0, acc = 0) => hpAdd(sbj, acc);
const wrapperSub = (sbj = 0, acc = 0) => sub(sbj, acc);
const wrapperMul = (sbj = 0, acc = 0) => mul(sbj, acc);
const wrapperDiv = (sbj = 0, acc = 0) => div(sbj, acc);

// 四舍五入
const rounds = (val: number, ex: number) => round(val, ex);


export default {
  add: wrapperAdd,
  sub: wrapperSub,
  mul: wrapperMul,
  div: wrapperDiv,
  rounds
};

那么项目中就可以完成了rounds的用法了
在这里插入图片描述
在这里插入图片描述
const exclTaxCurAmt = maths.rounds(mul(exclTaxAmt, exchangeRate), toFixedAmt); // 不含税金额本位币
或者下图的样式写法,其实是一样的
在这里插入图片描述
这样就完成了。即可以计算,也可以正常保留小数位。

切记切记,金额计算不要用toFixed()方法。

银行家算法(Banker's Rounding)也称为四舍六入五成双,是一种用于进行数字舍入的算法。在该算法中,当要舍弃的数字为5时,有两种情况: 1. 如果5后面的数字非零,则向上进位; 2. 如果5后面的数字是零,并且5前面的数字是偶数,则向下舍弃;如果5前面的数字是奇数,则向上进位。 这种算法的目的是在进行舍入时,尽量保持结果更加均衡。它常用于金融领域,以避免舍入误差对计算结果产生影响。 以下是一个使用 JavaScript 实现银行家算法的示例代码: ```javascript function bankerRound(num) { var rounded = num.toFixed(2); // 将数字保留两位小数 // 取小数部分的最后一位数字 var lastDigit = parseInt(rounded.charAt(rounded.length - 1)); // 取小数部分的倒数第二位数字 var secondLastDigit = parseInt(rounded.charAt(rounded.length - 2)); // 如果最后一位数字大于等于5,则进行进位 if (lastDigit >= 5) { // 如果最后一位数字是5,并且倒数第二位数字是偶数,则向下舍弃 if (lastDigit === 5 && secondLastDigit % 2 === 0) { rounded = (Math.floor(num * 100) / 100).toFixed(2); } else { rounded = (Math.ceil(num * 100) / 100).toFixed(2); } } return parseFloat(rounded); } // 示例用法 var num = 1.235; var roundedNum = bankerRound(num); console.log(roundedNum); // 输出 1.24 ``` 这段代码实现了银行家算法的逻辑,对给定的数字 `num` 进行四舍六入五成双舍入,并返回舍入后的结果。注意,该算法将数字保留两位小数,因此返回值为浮点数类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值