UNPKG

calculator-by-str

Version:

Enter the string formula to get the calculation result.

593 lines (545 loc) 18.5 kB
'use strict'; /** * 此算法用于计算 字符串公式 得到最后结果 * 公式 支持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 };