js 浮点运算及 toFixed 的问题

似乎所有的语言都存在浮点运算的问题,javascript 也不例外,与 java 等后台语言不同,javascript 长久以来缺少足够的API及生态去解决这样的问题。前端只负责展示,不负责计算,一直以来是前后端的共识,但前端也不可能完全规避计算数值的情况。

一 问题起因

数字最终会被计算机转成二进制,以二进制的形式进行运算。

vbscript 复制代码
0.1的二进制值如下:
0.1.toString(2) // "0.0001100110011001100110011001100110011001100110011001101"

因为位数太长,计算机存储二进制的数值时会有部分丢失,再转成十进制时产生误差。

二 消除误差的加法

javascript 复制代码
function add(val,val2){
    if(typeof(val) !== 'number' || isNaN(val) ) return NaN;
    if(typeof(val2) !== 'number' || isNaN(val2) ) return NaN;

    var presicion1, presicion2, maxPresicion;
    presicion1 = val.toString().split(".")[1]?.length ? val.toString().split(".")[1].length : 0;
    presicion2 = val2.toString().split(".")[1]?.length ? val2.toString().split(".")[1].length : 0;

    maxPresicion = Math.pow(10,Math.max(presicion1, presicion2));

    return (val * maxPresicion + val2 * maxPresicion) / maxPresicion;
}

这个方法消除了浮点误差,没有做精度上的处理。 js 的精度控制普遍用 toFixed() ,但是 toFixed 方法本身存在问题,一般说是"四舍六入五成双",但是不同浏览器下呈现的效果又五花八门,如果从事金融行业,数值以百万或亿为单位,这样弄错一个小数点的代价将十分昂贵。

三 改写toFixed

javascript 复制代码
/**
 * Number.prototype.toFixed 原生方法取精度是 "四舍六入五成双"的原则,但是即便这样也很不准确,在不同浏览器下呈现不同的表现方式,混乱至极。
 * 下面方面四舍五入取精度
 */
function roundToFixed (number, precision) { 
    let numStr = number.toString(); 
    if (!precision) precision=0; 
    if (numStr.indexOf(".") === -1)  numStr =`${numStr}.${new Array(precision+1).join("0")}`;

    // 包含以正负号开头的情况,举例:123.4567
    if(new RegExp(`^(-|\\+)?(\\d+(\\.\\d{0,${precision + 1}})?)\\d*$`).test(numStr)) {
        const sign = RegExp.$1; // (-|\\+) 正负号
        let numStrReg = `0${RegExp.$2}`; // 0123.4567
        let afterDotLength = RegExp.$3.length; // .4567; 包含.在内 length = 5
        let flag = true; // 0123.4567 => 0的位置是否需要替换

        if ( afterDotLength === precision + 2 ) {
            const numStrArr = numStrReg.match(/\d/g); // numStrReg分成的数组 ['0', '1', '2', '3', '4', '5', '6', '7']
            // 如果最后一位数字大于4,进1
            if (parseInt(numStrArr[numStrArr.length - 1]) > 4) {
                for (let i = numStrArr.length-2; i >= 0; i -= 1) {
                    numStrArr[i] = parseInt(numStrArr[i]) + 1;
                    // 等于10时继续遍历,否则直接跳出
                    if (numStrArr[i] === 10) {
                        numStrArr[i] = 0;
                        flag = i !== 1;
                    } else {
                        break;
                    }
                }
            }
            numStrReg = numStrArr.join("").replace(new RegExp(`(\\d+)(\\d{${ precision }})\\d$`), "$1.$2"); // 0123.4567
        }
        if (flag) numStrReg = numStrReg.substr(1); // 123.4567
        return (sign + numStrReg).replace(/\.$/, ""); // 以.结尾的情况
    }
    return number;
};
相关推荐
雪隐7 分钟前
个人电脑玩AI-15让5060 Ti给你打工——MiniMax H3 本地部署实录:一个自带录音棚的视频模型,和它的 NVFP4 瘦身奇遇
前端·人工智能·后端
GuWenyue39 分钟前
不用第三方SDK!Vue3原生Fetch实现DeepSeek流式输出,90%前端都会踩的分片解析坑一次性解决
前端·人工智能·llm
独隅41 分钟前
前端离线暂停更新策略:Service Worker 与 PWA 实战指南
前端
不如摸鱼去1 小时前
Wot UI 2.3.0 发布:二维码组件来了,Open Wot 与 wot-starter 同步更新
前端·ui·微信小程序·前端框架·uni-app
Cloud_bread2 小时前
从ReAct到自主闭环:Agentic Coding核心执行引擎的技术演进
前端·react.js·前端框架
cidy_982 小时前
OptMem 使用教程
前端
满栀5852 小时前
原生JS + ECharts 多图表可视化实战:从业务实现到工程化优化
开发语言·javascript·echarts
杉氧2 小时前
用 Compose 挑战交互与动效天花板:ComposeCraftLab 开源实验室全解析
android·前端·kotlin
Canace2 小时前
AI 生成到 90% 突然断了:你的解决方案是?
前端·人工智能
TinssonTai2 小时前
Vite 8 版 Chrome 插件全家桶,popup/options/sidepanel 一次集齐
前端·vue.js