JS toFixed的使用误差,银行家算法(四舍六入五取偶)实现

本文探讨了JavaScript的toFixed方法在保留小数位数时的不一致行为,特别关注银行家算法实现的四舍六入五取偶规则,并揭示了浏览器间的差异。通过银行家算法实例和解决浮点数计算误差的方法,提供了理解和应对策略。
摘要由CSDN通过智能技术生成

JS toFixed的使用误差,银行家算法(四舍六入五取偶)实现

前言

JS是一门弱语言,在进行保留有效数字时,特别是对于金额等要求较高的计算时,其诡异的处理是我们需要考虑的重点,一般的使用四舍五入即可,这里记录的是在研究时碰见的toFixed的诡异误差而引起的思考

toFixed方法

官方介绍是:一个四舍六入五取偶的方法(也叫银行家算法)

“四舍六入五取偶”

对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位

  • “四”是指≤4 时舍去,"六"是指≥6时进上;
  • "五"指的是根据5后面的数字来定:
    • 当5后有有效数字时,舍5入1;
    • 当5后无有效数字时,需要分两种情况来讲:
      • 5前为奇数,舍5入1;
      • 5前为偶数,舍5不进。(0是偶数)

问题

在不同浏览器中会有不同的表现,这在实际使用中对于用户是极度不友好的

var a = 1.335;
console.log(a.toFixed(2))

===================
// IE         1.34
//chorme   1.33

而单在chorme中测试,发现其结果也是与描述不符的,这里列举一些尝试内容

var b = 1.335
b.toFixed(2)
"1.33"

var b = 1.345
b.toFixed(2)
"1.34"

var b = 1.355
b.toFixed(2)
"1.35"

var b = 1.365
b.toFixed(2)
"1.36"

var b = 1.375
b.toFixed(2)
"1.38"

var b = 1.385
b.toFixed(2)
"1.39"

银行家算法实现

这里简单地提供了一种实现方式

/**
 * 银行家算法(四舍六入五取偶) 保留1-14
 * @param value 被截取的数字
 * @param num 保留小数的位数 使用parseFloat 最多 1到14(第16位小数会截取,15准确,但是判断需要用到身后一位)
 */
var bankCalculate = function (value, num) {
    //参数校验
    value = parseFloat(value);
    if (value !== value) {
        console.log("bankCaculate参数非数值");
        return value;
    }
    value = value + "";
    num = parseInt(num) !== parseInt(num) ? 0 : parseInt(num);
    if (num < 1) num = 1;
    if (num > 14) num = 14;

    //分离整数与小数
    var intValue = value.split(".")[0];
    var floatValue = value.split(".")[1];
    //如果小数部分长度少于或者等于要保留的位数 直接返回toFixed值即可
    if (!floatValue || floatValue.length <= num) return parseFloat(value).toFixed(num);

    //计算返回值
    var result = parseFloat(intValue + "." + floatValue.substring(0, num));
    var toCheck = floatValue.substring(num);
    if(parseInt(toCheck.substring(0,1)) < 5) return result + "";
    //如果小数部分长度仅比保留位数多一位 代表后位为0
    if (floatValue.length === num + 1) {
        var prev = floatValue.substring(num - 1, num);
        if(parseInt(toCheck) === 5 && prev % 2 === 0){
            return result + "";
        }
    }
    //浮点数值直接相加会有精度丢失,这里的accAdd是一个写好的加法运算
    return accAdd(result, Math.pow(10,0 - num))  + "";
};

扩展

对于浮点数,直接在JavaScript中进行加减乘除四则运算会出现很大的误差。

原因

计算机中将十进制的数据转化为二进制进行计算,而对于浮点型的数据,转化成二进制之后可能会变成一个无限循环的数字,这在计算机中是不允许的,所以就要对数据做截断,此时的数据运算之后再转成十进制就可能会产生极大的误差。

PS:并不是只JS存在这种问题,Java、C++也都如此,只不过他们内部都有封装好的方法来解决这个问题。JS是一门弱语言,没有做类似的操作。

解决方式

这里仅提供思路,我们应学习问题的解决方式,而不是背代码

//以上面代码中使用parseFloat与parseInt举例
parseInt(999999999999999)//999999999999999 15位
parseInt(9999999999999991)//9999999999999992 16位
999999999999999 + 999999999999999//1999999999999998
//可以看到15位内的整数加法是准确的(实际应该是Math.pow(2, 53)即9007199254740992)
//而上面说过parseFloat的小数部分也是15位精确的,那么我们就可以将其先去除小数再进行运算后恢复小数即可
function add(num1, num2) {
  var r1, r2, m;
  try {
    r1 = num1.toString().split('.')[1].length;
  } catch(e) {
    r1 = 0;
  }
  try {
    r2 = num2.toString().split(".")[1].length;
  } catch(e) {
    r2 = 0;
  }
  m = Math.pow(10,Math.max(r1,r2));
  return (num1*m+num2*m)/m;
}

附注

看到这里大家应该会发现,上面的两段代码还是有问题的,对于parseFloat转换后,有效数字超过15位的值仍然有问题,这里不再继续深入说明,以上只是一些对于该问题的思考与拙见

感谢您的阅读!

银行家算法(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` 进行四舍六入五成双舍入,并返回舍入后的结果。注意,该算法将数字保留两位小数,因此返回值为浮点数类型。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值