calculator-by-str
Version:
Enter the string formula to get the calculation result.
593 lines (545 loc) • 18.5 kB
JavaScript
;
/**
* 此算法用于计算 字符串公式 得到最后结果
* 公式 支持Math里面所有值 和 简单的 加减乘除 已经随意的
*/
const selfName = "calculator-by-str";
const round = (a) => Math.floor(Number(a) + 0.5)
const mod = (a, b) => a % b
/**
* 支持的函数 和值
*/
const formulas = {
"Math.abs": [Math.abs, 1],
"Math.acos": [Math.acos, 1],
"Math.asin": [Math.asin, 1],
"Math.atan": [Math.atan, 1],
"Math.atan2": [Math.atan2, 2],
"Math.ceil": [Math.ceil, 1],
"Math.cos": [Math.cos, 1],
"Math.floor": [Math.floor, 1],
"Math.log": [Math.log, 1],
"Math.max": [Math.max, 2], // max 为了和其他语言同步 改成两个 如果需要用多个参数 可以把2改成"more"
"Math.min": [Math.min, 2], // min 为了和其他语言同步 改成两个 如果需要用多个参数 可以把2改成"more"
"Math.pow": [Math.pow, 2],
"Math.round": [round, 1],
"Math.sin": [Math.sin, 1],
"Math.sqrt": [Math.sqrt, 1],
"Math.tan": [Math.tan, 1],
"Math.mod": [mod, 2], // 非Math方法
"Math.PI": [Math.PI],
// "Math.SQRT2": [Math.SQRT2],
// "Math.LN10": [Math.LN10],
// "Math.LOG10E": [Math.LOG10E],
// "Math.LOG2E": [Math.LOG2E],
// "Math.SQRT1_2": [Math.SQRT1_2],
}
// 支持小写 实际上代码运行时会把公式里面的所有英文转成小写 统一都用小写
const keys = Object.keys(formulas)
for (const key of keys) {
formulas[key.toLowerCase()] = formulas[key]
}
/**
* 测试log
*/
const myLog = () => {
// console.log(selfName, ...arguments)
}
/**
* 测试日志函数
*/
const myLog2 = (...args) => {
console.log(selfName, ...args)
}
/**
* 加减最后算
*/
const simple = {
["+"]: (a, b) => a + b,
["-"]: (a, b) => a - b
}
/**
* 乘除先算
*/
const simple2 = {
["*"]: (a, b) => a * b,
["/"]: (a, b) => a / b,
// ["%"]: (a, b) => a % b
}
/**
* 乘方先算
*/
const simple3 = {
["**"]: (a, b) => a ** b
}
/**
* 1. 先检查公式是否正常
* 1. 检查括号是否正确
* @param {*} fmStr
* @returns
*/
const checkFm = (fmStr) => {
let layer = 0
for (const i of fmStr) {
if (i == "(") {
layer += 1
} else if (i == ")") {
layer -= 1
}
}
return layer
}
/**
* 如果去掉两边的括号后还是个正确的公式 就先去掉两边的括号
* @param {*} fmStr
* @returns
*/
const formatFm = (fmStr) => {
const olddata = fmStr
if (fmStr[0] == "(" && fmStr[fmStr.length - 1] == ")") {
fmStr = fmStr.slice(1, fmStr.length - 1)
}
if (!checkFm(fmStr)) {
return olddata
}
return fmStr
}
/**
* 处理-0转字符串的时候自动变成0 的问题
* @param {*} zero
* @returns
*/
const getRealZero = (zero) => {
if (zero == 0 && 1 / zero == -Infinity) {
return "-0"
}
return zero
}
/**
* 处理公式 这里面一定没有Math.值
* @param {*} fmStr 公式Str
* @param {*} key
* @returns
*/
const getFmRet = (fmStr, key) => {
const olddata = fmStr
const [func, pcnt] = formulas[key];
const pos = fmStr.indexOf(key)
myLog(fmStr, "ret------", fmStr, key, pos)
if (pos > -1) {
myLog(fmStr, "ret------", key, pos)
let endPos = 0
let layer = 0
for (const i of fmStr) {
if (endPos > pos) {
myLog(i)
if (i == "(") {
layer += 1
} else if (i == ")") {
layer -= 1
}
}
endPos += 1
myLog("endPos", endPos, pos, layer, key.length + 1)
if (endPos > pos + key.length + 1 && layer == 0 && endPos > 1) {
break
}
}
myLog("endPos2 ", endPos, pos, layer, pcnt)
const pos2 = endPos - 1
let newData = fmStr.slice(pos + key.length + 1, pos2)
newData = formatFm(newData)
let datas = newData.split(",")
if (datas.length !== pcnt && pcnt != "more") {
throw new Error('参数数量错误' + fmStr)
}
let parms = []
myLog("newData", { newData, pos, length: key.length, pos2, datas });
for (const cdata of datas) {
myLog("ret------1", cdata)
const ret = getKuohao(cdata)
myLog(ret, "ret------", cdata)
if (parseFloat(ret) == ret || ret == Infinity || ret == -Infinity) { // 可以直接用
parms.push(ret)
} else if (isNaN(ret)) {
return ret
} else {
parms.push(fmFunc(ret))
}
}
myLog("parms------", parms)
let ret
if (func instanceof Function) {
ret = func(...parms)
myLog("ret 1 ", [ret]);
ret = getRealZero(ret)
myLog("ret 1 ", [ret]);
fmStr = fmStr.replace(key + "(" + newData + ")", ret)
myLog("ret 1 ", [ret]);
} else {
ret = func
fmStr = fmStr.replace(key + newData, ret)
}
myLog("ret", [ret, parms, datas, newData]);
myLog("getFmRet - ret ", fmStr);
}
if (olddata != fmStr) {
return getFmRet(fmStr, key)
}
return fmStr
}
/**
* 查找公式里面最近的符号
* @param {*} fmStr 公式
* @param {*} isL 是否左边
* @param {*} list 符号列表
*/
const findSpFm = (fmStr, isL, list) => {
let retPos
let retKey
for (const key of list) {
let curPos
if (isL) {
curPos = fmStr.lastIndexOf(key)
if (curPos > -1) {
if (!retPos || retPos < curPos) {
retPos = curPos
retKey = key
}
}
} else {
curPos = fmStr.indexOf(key)
if (curPos > -1) {
if (!retPos || retPos > curPos) {
retPos = curPos
retKey = key
}
}
}
}
return [retPos, retKey]
}
/**
* 取乘除号左边的数值 可以不管前面的符号 后面可以处理
* 左边只需要处理+-
* @param {*} fmStr 分割称号除号后的左边的数据 需要取右边的值
* @returns
*/
const getLeftData = (fmStr) => {
if (parseFloat(fmStr) == fmStr) {
return [fmStr, 0]
}
const [retPos] = findSpFm(fmStr, true, Object.keys(simple))
myLog("fmStr Left - - - - ", [fmStr, retPos, fmStr.slice(retPos + 1)])
if (retPos > -1) {
return [fmStr.slice(retPos + 1), retPos + 1]
}
return [fmStr, 0]
}
/**
* 取乘除号右边边的数值
* 右边需要处理 加减乘除
* @param {*} fmStr 分割称号除号后的右边边的数据 需要取左边的值
* @returns
*/
const getRightData = (fmStr) => {
// 由于parseFloat 可能会把-0 变成0, 所以特别加一个这个处理。 如果fmStr = -0 , parseFloat(fmStr) == fmStr => true
if (parseFloat(fmStr) == fmStr) {
return fmStr
}
return parseFloat(fmStr) + ""
}
/**
* 加减计算
* 加减计算是最后计算的,此时公式里面已经没有了Math函数和乘除法,也没有了括号
* 处理思路
* 先分割成 [num,+ or -,num,+ or -,num,...] 这种格式
* 然后shift出前3个值把计算价格再unshift进第一个
* 注意
* 1. 此时如果两个数之间有两个或者多个符号 会直接根据负负得正的原理 变换好最终符号 再进行计算
* 如果最后一个是符号的话,直接push进去一个0,或者不把这个符号push进去也行,如果最后有符号,也有可能是前面的计算有问题,这里做兼容处理
*/
const getSimple = (fmStr) => {
let list = []
let curnum = ""
let curFm = ""
myLog("getSimple", fmStr, parseFloat(fmStr))
// 分割成 [num,+ or -,num,+ or -,num,...] 这种格式
for (const i in fmStr) {
const s = fmStr[i]
if (simple[s] && !(simple[s] && curnum[curnum.length - 1] == "e")) {
const r = list[list.length - 1]
const func = simple[r]
myLog("r - ", i, r, !!func, list, list.length - 1, "s1 - " + s)
if (func && curnum == "") { // 需要变化符号
if (r == s) {
list[list.length - 1] = "+"
} else {
list[list.length - 1] = "-"
}
} else {
if (curnum == "") {
list.push("0")
} else {
list.push(curnum)
}
list.push(s)
curnum = ""
}
} else {
curnum += s
}
if (i == fmStr.length - 1) {
if (!simple[s]) {
list.push(curnum)
}
}
myLog("结果 ", curnum, list, curFm, "s2 - " + s)
}
// 最后一个字符是符号 兼容一下算法, 可能前面有问题
if (simple[list[list.length - 1]]) {
list[list.length] = "0"
}
myLog("getSimple-list", list)
// 依次计算
let ret = 0
const forCnt = (list.length - 1) / 2
for (let index = 0; index < forCnt; index++) {
const oneNum = list.shift()
myLog(oneNum, "oneNum")
const funcs = list.shift()
myLog(funcs, "funcs")
const twoNum = list.shift()
myLog(twoNum, "twoNum")
if (simple[funcs]) {
ret = simple[funcs](parseFloat(oneNum), parseFloat(twoNum))
list.unshift(ret)
myLog("ret", list, ret)
} else {
myLog("error", oneNum, funcs, twoNum, list)
}
}
myLog(ret)
myLog(list, fmStr)
return ret
}
/**
* 计算乘除法,前提是现在已经没有了函数和括号 只有乘除加减计算
* 思路
* 找到最近的*或/,谁进就先计算谁
* 1. 找到了,以符号分割split
* 1. 再找左边的list[0]里面的最右边的数 这个就是找最右边的-和+号,找最近的位置分割拿到数字
* 2. 再找右边的list[1]里面的最左边的数 这个就直接 parseFloat 就能去除
* 3. 找到两个参数就直接计算出结果
* 4. 最后组合好左边右边剩余的数据,这里可能还有乘除法,所以再递归计算一次
* 2. 如果没有找到,就直接计算+—法,返回加减法的值,就ok了
*
* @param {*} fmStr
* @returns
*/
const getSimple2 = (fmStr) => {
const olddata = fmStr
myLog("fmStr-1", fmStr, parseFloat(fmStr), parseFloat(fmStr) == fmStr)
if (parseFloat(fmStr) == fmStr) {
return fmStr
}
// 找到最近的乘号或者除号
const [minPos, curKey] = findSpFm(fmStr, false, Object.keys(simple2))
if (minPos > -1) {
const key = curKey
const datas = fmStr.split(key)
const simple2Func = simple2[key]
const leftData = getLeftData(datas[0])
// 左边的值需要返回位置和数据,如果返回的是Array
const [leftnum, leftpos] = leftData
const rightData = getRightData(datas[1])
const rightnum = rightData
const rightpos = rightData.length
myLog("getSimple0", [key, datas, leftData, rightData, leftpos, rightpos, rightnum]);
myLog("getSimple0 -- ", [parseFloat(leftnum), parseFloat(rightnum), parseFloat(leftnum) / parseFloat(rightnum)]);
if (key == "/" && parseFloat(rightnum) == 0) {
throw new Error('除0错误' + fmStr)
}
let ret = simple2Func(parseFloat(leftnum), parseFloat(rightnum))
ret = getRealZero(ret)
myLog("getSimple1", [ret.toString(), datas, leftnum, rightnum]);
datas[0] = datas[0].slice(0, leftpos)
datas[1] = datas[1].slice(rightpos)
for (const i in datas) {
if (i > 1) {
datas[1] += key + datas[i]
}
}
myLog("getSimple2", [ret, ret.toString(), datas, fmStr]);
myLog("getSimple2---", [datas, fmStr, datas[0] + key + datas[1], fmStr, datas[0] + key + datas[1] == fmStr]);
if (datas[0] + key + datas[1] == fmStr) {
return ret.toString()
} else {
fmStr = datas[0] + ret.toString() + "" + datas[1]
}
myLog("getSimple2---ret2 - ", fmStr);
}
myLog("fmStr-", fmStr, olddata, parseFloat(fmStr) == fmStr)
if (parseFloat(fmStr) == olddata) {
return fmStr
}
if (olddata != fmStr) {
return getSimple2(fmStr)
} else if (parseFloat(fmStr) == fmStr) {
return fmStr
} else {
return getSimple(fmStr)
}
}
/**
* 计算乘方 ** 弃用 使用 Math.pow
* @param {*} fmStr
*/
function getSimple3(fmStr) {
let pos = fmStr.indexOf("**")
if (pos > -1) {
let leftnum = ""
let curPos
for (let index = pos - 1; index > -1; index--) {
const s = fmStr[index];
myLog(s)
if (s == "-" || s == "e" || (s == "+" && fmStr[index-1] == "e") || s == "." || !isNaN(s)) {
leftnum = s + leftnum
} else {
curPos = index
break
}
}
let rightNum = parseFloat(fmStr.slice(pos + 2))
let leftstr = fmStr.slice(0, curPos + 1)
myLog("getSimple3", { fmStr, leftstr, rightNum, leftnum, rightStr: fmStr.slice(pos + 2) });
return getSimple2(fmStr.slice(0, curPos + 1) + Math.pow(leftnum, rightNum))
} else {
return getSimple2(fmStr)
}
}
/**
* 括号里面只要加减乘除 必须算完才返回
* @param {*} fmStr
* @returns
*/
const getKuohao = (fmStr) => {
fmStr = formatFm(fmStr)
const olddata = fmStr
const pos = fmStr.lastIndexOf("(")
if (pos > -1) {
const pos2 = fmStr.indexOf(")", pos)
const newData = fmStr.slice(pos + 1, pos2)
myLog(newData);
const ret = getSimple3(newData)
fmStr = fmStr.replace("(" + newData + ")", ret)
myLog("getKuohao", newData, ret);
}
if (olddata != fmStr) {
return getKuohao(fmStr)
}
return getSimple3(fmStr)
}
const getMathFm = (fmStr = "") => {
const mathP = fmStr.lastIndexOf("math") // 确定这个是最后一个Math 从后往前面计算就不会出现函数包含函数的情况
let curKay
myLog("getMathFm fmStr", fmStr, mathP)
for (const key in formulas) {
const mathPend = fmStr.indexOf(key, mathP)
let [func, pcnt] = formulas[key]
if (mathPend > -1 && (mathPend == fmStr.indexOf(key + "(", mathP) || !(func instanceof Function)) // 如果是函数那一定是 函数名字加括号的形式 // 否则不是函数 否则不是函数
) {
myLog("key", key, mathPend, mathP)
curKay = key
break
}
}
if (curKay) {
const [func, _] = formulas[curKay]
if (func instanceof Function) {
let endPos = 0
let layer = 0
// 找最终截断位置
let adds = ""
for (const i of fmStr) {
if (endPos >= mathP) {
myLog(i)
if (i == "(") {
layer += 1
} else if (i == ")") {
layer -= 1
}
adds += i
}
endPos += 1
myLog("endPos", curKay, endPos, mathP, layer, curKay.length + 1, adds)
if (endPos > mathP + curKay.length + 1 && layer == 0 && endPos > 1) {
break
}
}
const curFm = fmStr.slice(mathP, endPos)
const ret = getFmRet(curFm, curKay)
// if (isNaN(ret)) {
// return NaN
// }
myLog("curKay,fmStr", { curFm, curKay, fmStr, ret, mathP, mathP, endPos })
const starts = fmStr.slice(0, mathP)
const ends = fmStr.slice(mathP + curFm.length)
myLog("starts ----1 ", { fmStr, starts, ends })
fmStr = starts + ret + ends
myLog("starts ----2 ", { fmStr, starts, ends })
// fmStr = fmStr.replace("(" + ret + ")", ret)
myLog("fmStr.replace2", { curKay, fmStr, curFm, ret })
// fmStr = ""
} else {
const curNum = func
const starts = fmStr.slice(0, mathP)
const ends = fmStr.slice(mathP + curKay.length)
fmStr = starts + curNum + ends
fmStr = fmStr.replace("(" + curNum + ")", curNum)
myLog("[mathP,mathPend]", [mathP, curKay, fmStr, "(" + curNum + ")"])
}
return getMathFm(fmStr)
} else {
return fmStr
}
}
/**
* 根据字符串公式 计算出结果
* 思路
* 1. 先检查公式是否正常
* 2. 分级执行算法
* 1. 去掉括号
* */
const fmFunc = (fmStr = "", type = 2) => {
const oldFmStr = fmStr
try {
if (typeof fmStr !== "string") {
console.log("error -- typeof fmStr !== string", typeof fmStr)
return NaN
}
fmStr = fmStr.replace(" ", "")
fmStr = fmStr.toLowerCase()
//
fmStr = formatFm(fmStr)
const layer = checkFm(fmStr)
if (layer !== 0) {
myLog("exit err {", layer);
return ""
}
// 先计算 公式formulas
fmStr = getMathFm(fmStr)
// if (isNaN(fmStr)) {
// return NaN
// }
myLog("getMathFm", fmStr)
// 再计算最里面的括号
const ret = getKuohao(fmStr)
myLog("getKuohao", ret)
return parseFloat(ret)
} catch (e) {
console.log("error -- ", oldFmStr, fmStr, e)
return NaN
}
}
module.exports = { fmFunc };