一、前言
众所周知,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/...
总结
如果对您有帮助,请一键三连,完结撒花。。。