表达式求值并处理精度问题
eval 能跑,对账对不上
公司项目要算公式,进来的都是字符串,0.1 + 0.2 这种,前端算完返回。
我偷懒用了 eval,跑是跑得通,测了一下 0.1 + 0.2,出来 0.30000000000000004,对端对账对不上。
装了 babel 插件也没用
皮佬有个 babel-plugin-arithmetic,编译期能把四则运算换成精确版本,我装上了。eval 跑的是运行时字符串,babel 编译阶段碰不到,插件帮不上忙,只能自己写,函数长这样
function formula(str) {}
formula(`0.1 + 0.2`) // 0.3
递归下降解析
先把字符串拆开再算。递归下降,从左往右扫,找当前层级最低的运算符。加减设成 1,乘除设成 2,碰到 ( 给 temp 加 100,括号里的优先级整体抬高,) 再减回去。找到运算符,式子切两半,左右各自递归,最后用精确四则函数合并。
function formula(list) {
return calc(list, 0, list.length - 1)
}
function calc(s, l, r) {
let op = -1,
pri = 1000 - 1,
cur_pri,
temp = 0,
i = l
while (i <= r) {
cur_pri = 1000
switch (s[i]) {
case '+':
case '-':
cur_pri = 1 + temp
break
case '*':
case '/':
cur_pri = 2 + temp
break
case '(':
temp += 100
break
case ')':
temp -= 100
break
}
if (cur_pri <= pri) {
pri = cur_pri
op = i
}
i++
}
if (op === -1) {
const val = s.slice(l, r + 1).trim()
if (/^[+-]?(0|([1-9]\d*))(\.\d+)?$/.test(val)) {
return parseFloat(val)
}
let num = 0
for (let i = l; i <= r; i++) {
if (s[i] < '0' || s[i] > '9') continue
num = num * 10 + (s[i] - '0')
}
return num
}
const a = calc(s, l, op - 1)
const b = calc(s, op + 1, r)
switch (s[op]) {
case '+':
return accAdd(a, b)
case '-':
return accSub(a, b)
case '*':
return accMul(a, b)
case '/':
return accDiv(a, b)
}
return 0
}
运算部分不能写 + - * /,accAdd、accSub、accMul、accDiv 从 babel-plugin-arithmetic 的 calc.js 拷的。插件白装了,这几个函数还能用。
业务里的式子跑完,对账对上了。eval 之后再没碰过。