表达式求值并处理精度问题

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 之后再没碰过。