zoukankan      html  css  js  c++  java
  • js中toFixed精度问题的原因及解决办法

    toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。例如将数据Num保留2位小数,则表示为:toFixed(Num);但是其四舍五入的规则与数学中的规则不同,使用的是银行家舍入规则,银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。具体规则如下:简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。

    经测试发现,在chorme下面,并没有完全遵守这个规则,尤其是5的后面没有数字的时候,不是这么判断的,如下:

    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"

    可以发现在chorme下没有完全去遵循这个规律,或许它有自己的算法,但是毕竟它没有遵循通用的银行家算法,所以tofixed这个方法在涉及到金钱计算的业务中还是少用.

    总而言之:不论引入toFixed解决浮点数计算精度缺失的问题也好,它有没有使用银行家舍入法也罢,都是为了解决精度的问题,但是又离不开二进制浮点数的环境,但至少他帮助我们找到了问题所在,从而让我们有解决方法。

    一开始的办法是把要四舍五入的后一位单独拎出来单独判断。

    解决方法:

    通过重写toFixed方法:

    Number.prototype.toFixed = function (n) {

    let result = number.toString();

    const arr = result.split('.');

    const integer = arr[0];

    const decimal = arr[1];

    result = integer + '.' + decimal.substr(0, n);

    const last = decimal.substr(n, 1);

    // 四舍五入,转换为整数再处理,避免浮点数精度的损失

    if (parseInt(last, 10) >= 5) {

    const x = Math.pow(10, n);

    result = ((parseFloat(result) * x) + 1) / x;

    result = result.toFixed(n);

    }

    return result;

    }

    然后又发现计算机二进制编码导致的精度问题,详见上一篇博客。

    自己debugger,发现页面中的js进了死循环。很明显问题出在toFixed中回调了toFixed,结果没有走出来,继续debugger,又有了惊人的发现。以下是控制台测试:

    console.log(2.115 * 100) // 211.50000000000003

    console.log(2.0115 * 1000) // 2011.4999999999998

    既然你一直进入循环,我就手动把你拉出来。

    result = (Math.round((parseFloat(result)) * x) + 1) / x;

    最终完整的重写toFixed的方法

    // toFixed兼容方法

    Number.prototype.toFixed = function (n) {

        if (n > 20 || n < 0) {

            throw new RangeError('toFixed() digits argument must be between 0 and 20');

        }

        const number = this;

        if (isNaN(number) || number >= Math.pow(10, 21)) {

            return number.toString();

        }

        if (typeof (n) == 'undefined' || n == 0) {

            return (Math.round(number)).toString();

        }

        let result = number.toString();

        const arr = result.split('.');

        // 整数的情况

        if (arr.length < 2) {

            result += '.';

            for (let i = 0; i < n; i += 1) {

                result += '0';

            }

            return result;

        }

        const integer = arr[0];

        const decimal = arr[1];

        if (decimal.length == n) {

            return result;

        }

        if (decimal.length < n) {

            for (let i = 0; i < n - decimal.length; i += 1) {

                result += '0';

            }

            return result;

        }

        result = integer + '.' + decimal.substr(0, n);

        const last = decimal.substr(n, 1);

        // 四舍五入,转换为整数再处理,避免浮点数精度的损失

        if (parseInt(last, 10) >= 5) {

            const x = Math.pow(10, n);

            result = (Math.round((parseFloat(result) * x)) + 1) / x;

            result = result.toFixed(n);

        }

        return result;

    }

  • 相关阅读:
    ES 遇到 unassigned shard如何处理?
    elasticsearch如何安全重启
    Agg学习笔记
    二进制文件中读写结构体
    C语言 结构体数组保存到二进制文件中
    Memcache 笔记
    memcached完全剖析–1. memcached的基础
    Redis和Memcache对比及选择
    Exploring the MapBox stack: MBTiles, TileJSON, UTFGrids and Wax
    Tilemill + tilestream + mapbox.js 自制地图
  • 原文地址:https://www.cnblogs.com/ranyonsue/p/11388281.html
Copyright © 2011-2022 走看看