LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

js计算精度溢出,自定义加减乘除类

freeflydom
2025年9月10日 10:29 本文热度 233

前言

众所周知,javascript因为底层原因存在计算精度问题,这里就不做过多赘述;如果不理解,可以先去看看我以前写的文章《js数学计算精度溢出问题》


优缺点

优点

  • 计算流程有日志可以追溯

  • 链式调用,方法可接受多参数

  • 接受数学表达式字符串

  • 计算精度可调节

  • 代码量小

  • ts/js版本

缺点

  • 只能够实现加减乘除的运算,对于其他复杂的运算,需要自己去加

核心代码

1)链式调用,核心方法

  // 初始化获取操作代理
    get operationProxy() {
        const proxy = {
            'add': CusMath.accAdd,
            'sub': CusMath.accSub,
            'mul': CusMath.accMul,
            'div': CusMath.accDiv,
        }
        return proxy
    }
    // 操作方法
    operation(type, ...args) {
        const that = this
        const operaFun = this.operationProxy[type]
        if (typeof operaFun === 'function' && that.isNotEmptyArr(args)) {
            this._processValue = args.reduce(function (a, b) {
                const val = operaFun(that.convertNumber(a), that.convertNumber(b))
                that.logPush({
                    operationName: operaFun?.name,
                    result: val,
                    type: 'chain',
                    params: [a, b]
                })
                return val
            }, this._processValue)
        }
        return this
    }

2)数学表达式计算核心方法

    // 表达式
    expression(expStr) {
        if (expStr && typeof expStr === 'string') {
            // 优先级,括号 > 乘除 > 加减
            const bool = this.validateExpression(expStr)
            if (bool) {
                this._original_expression = expStr
                this._process_expression = expStr
                this.expLogPush(this._process_expression)
                try {
                    const result = this.dealAndCalcFirstBracketArr()
                    this.expLogPush(result)
                    return result
                } catch (err) {
                    console.log(err?.message)
                }
            } else {
                throw new Error('无效数学表达式')
            }
        } else {
            throw new Error('数学表达式为字符串且不能为空')
        }
    }
    // 获取一级括号的计算表达式
    getFirstBracketExpress(newStr) {
        console.log('newStr=====>', newStr)
        const nStr = String(newStr)
        let matches = []
        if (nStr.indexOf('(') !== -1 && nStr.indexOf(')') !== -1) {
            const regex = /\(([^()]+)\)/g;
            matches = [...(newStr?.matchAll(regex) || [])].map(match => match[1]);
        }
        return matches
    }
    // 处理并计算一级括号的计算表达式
    dealAndCalcFirstBracketArr() {
        let val = 0
        const bracketArr = this.getFirstBracketExpress(this._process_expression)
        if (this.isNotEmptyArr(bracketArr)) {
            console.log('解析括号数组', bracketArr)
            for (let i = 0; i < bracketArr.length; i++) {
                const bracketExpStr = bracketArr[i]
                const result_val = this.calcFlatExpress(bracketExpStr)
                // 将括号内的表达式替换成最终计算的结果
                const regStr = '(' + bracketExpStr + ')'
                this._process_expression = this._process_expression.replace(regStr, result_val)
                this.expLogPush(this._process_expression)
                console.log(`括号字符串【${regStr}】替换为【${result_val}】得到新表达式为【${this._process_expression}】`)
            }
            // 递归循环去掉多层级的括号
            return this.dealAndCalcFirstBracketArr()
        } else {
            this.expLogPush(this._process_expression)
            val = this.calcFlatExpress(this._process_expression)
        }
        return val
    }
    // 计算平铺的表达式
    calcFlatExpress(expStr) {
        console.log('计算平铺的表达式', expStr)
        // 转换操作, +- => - ;  ++ => + ; -- => + ;  -+ => -
        const dealExpStr = expStr?.replace(/\s/g, '')?.replace(/\+\-/g, '-')?.replace(/\+\+/g, '+')?.replace(/\-\-/g, '+')?.replace(/\-\+/g, '-')
        // 使用正则表达式匹配所有数字和运算符
        const parts = dealExpStr.split(/([+-])/);
        // 挑出乘除的字符串
        let mulOrDivArr = []
        const cloneParts = JSON.parse(JSON.stringify(parts))
        cloneParts.forEach((ele, index) => {
            if (!['+', '-'].includes(ele) && Number.isNaN(Number(ele))) {
                mulOrDivArr.push({
                    ele,
                    index
                })
            }
        })
        let val = 0
        if (mulOrDivArr?.length) {
            for (let i = 0; i < mulOrDivArr.length; i++) {
                const { ele: expStr, index } = mulOrDivArr[i]
                const result = this.getFlatExpressResult(expStr)
                console.log('result', result)
                cloneParts.splice(index, 1, result)
            }
        }
        if (cloneParts?.length > 1) {
            const finalExpStr = cloneParts.join('')
            val = this.getFlatExpressResult(finalExpStr)
        } else {
            val = cloneParts[0] || 0
        }
        return val
    }
    // 无优先级的,从左到右计算,执行计算并返回计算结果
    getFlatExpressResult(expStr) {
        const parts = this.getExpParts(expStr)
        console.log('运算符拆解', parts)
        let val = 0
        if (this.isNotEmptyArr(parts)) {
            const expressKeyArr = Object.keys(this.expressionOperationProxy)
            const numArr = []
            const operaArr = []
            // 奇数是数字,偶数是计算操作符,
            for (let i = 0; i < parts.length; i++) {
                const ele = parts[i]
                const isOdd = this.isOddNumber(i + 1)
                if (isOdd) {
                    const isNum = this.isNumber(ele)
                    if (isNum) {
                        numArr.push(ele)
                    } else {
                        throw new Error(`存在非数字项【${ele}】`)
                    }
                } else {
                    if (expressKeyArr.includes(ele) && !isOdd) {
                        const operaFun = this.expressionOperationProxy[ele]
                        if (typeof operaFun === 'function') {
                            operaArr.push({
                                operationName: operaFun?.name,
                                operaFun,
                                index: i,
                            })
                        } else {
                            throw new Error(`expressionOperationProxy对象解析【${ele}】不是一个方法`)
                        }
                    } else {
                        throw new Error(`存在非数学运算符【${ele}】`)
                    }
                }
            }
            const operaArrLen = operaArr.length
            // 操作符不能为空, 如果为空则赋值第一个, 操作符相比于数字始终要少一个
            if (this.isNotEmptyArr(operaArr) && operaArr.length === numArr.length - 1) {
                while (operaArr.length) {
                    const arg1 = operaArrLen === operaArr.length ? numArr.shift() : val
                    const operaFunObj = operaArr.shift()
                    const arg2 = numArr.shift()
                    val = operaFunObj.operaFun(arg1, arg2)
                }
            } else {
                throw new Error(`逻辑错误`)
            }
        }
        return val
    }
    // 拆解运算表达式
    getExpParts(expStr) {
        const parts = expStr.match(/([0-9]+(\.[0-9]+)?)|([+\-*\/])/g);
        return parts || []
    }
    // 是否是表达式
    validateExpression(str) {
        const mulReg = /^([^*]|\*[^*])*$/
        const divReg = /^([^/]|\/[^/])*$/;
        const newStr = str.replace(/\s/g, '')
        // 不能出现连续的 ** 或者 // ,只能单个
        const mulBool = mulReg.test(newStr)
        const divBool = divReg.test(newStr)
        // 不能包含以下的其他特殊字符
        const rest = newStr.replace(/\d+|\+|\-|\*|\/|\.|\(|\)/g, '')
        return mulBool && divBool && !Boolean(rest)
    }

上述代码只是核心代码的简单介绍,详细请看源码

五、使用CusMath类

import { CusMath } from "./cusMath.js";
const cusMath = new CusMath()
// 第一种方式
const calcInstance = cusMath.operation('add', 3.251, 253.635, 1.25, 42).operation('sub', 63.652, 4.952)
const totalLogs = calcInstance._logs
const totalVal = calcInstance.end()
// console.log('totalLogs=====>', totalLogs) // 方法链式调用日志
console.log('totalVal=====>', totalVal)
// 第二种方式
const otherInstance = cusMath.add(3.251, 253.635, 1.25, 42).sub(63.652, 4.952)
const otherLogs = otherInstance._logs
const otherVal = otherInstance.end()
// console.log('otherLogs=====>', otherLogs) // 方法链式调用日志
console.log('otherVal=====>', otherVal)
// 第三种方式,接受表达式
const str = '((20.124 * 2.35 / 5.96 + 20.124 * 65 / 3 - 20.124 + 2.35 * 5.96) / 2.36) * 3.241 + 2.53 * 5.96'
// const str = '5 * 2 - 3 + 5 * 6 / 3 + 6 - 8 + 41'
// const str = '5 * 2 - (3 * (5 * 6 / 3 + 6) ) * 12 - 8 + 41'
// const str = '0.1+0.2'
const result = cusMath.expression(str)
console.log('resultexpression=====>', result)
const logs = cusMath._logs
console.log('运算公式演变logs=====>', logs)

六、源码地址

github: github.com/ArcherNull/…


该文章在 2025/9/10 10:32:43 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved