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

一、前言

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

二、效果为

三、优缺点

优点

  • 计算流程有日志可以追溯
  • 链式调用,方法可接受多参数
  • 接受数学表达式字符串
  • 计算精度可调节
  • 代码量小
  • ts/js版本

缺点

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

四、核心代码

1)链式调用,核心方法

javascript 复制代码
  // 初始化获取操作代理
    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)数学表达式计算核心方法

javascript 复制代码
    // 表达式
    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类

javascript 复制代码
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/...

总结

如果对您有帮助,请一键三连,完结撒花。。。

相关推荐
JuneXcy1 小时前
循环高级(1)
c语言·开发语言·算法
MediaTea2 小时前
Python 第三方库:lxml(高性能 XML/HTML 解析与处理)
xml·开发语言·前端·python·html
西陵2 小时前
Nx带来极致的前端开发体验——使用MF进行增量构建
前端·javascript·架构
Nicholas682 小时前
flutter滚动视图之ProxyWidget、ProxyElement、NotifiableElementMixin源码解析(九)
前端
Ka1Yan2 小时前
什么是策略模式?策略模式能带来什么?——策略模式深度解析:从概念本质到Java实战的全维度指南
java·开发语言·数据结构·算法·面试·bash·策略模式
JackieDYH2 小时前
vue3中reactive和ref如何使用和区别
前端·javascript·vue.js
伍哥的传说2 小时前
解密 Vue 3 shallowRef:浅层响应式 vs 深度响应式的性能对决
javascript·vue.js·ecmascript·vue3.js·大数据处理·响应式系统·shallowref
绝无仅有3 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
ZZHow10243 小时前
React前端开发_Day4
前端·笔记·react.js·前端框架·web
地平线开发者3 小时前
开发者说|H-RDT:基于人类操作数据的跨本体机器人学习
算法·自动驾驶