Skip to content

实现 数学表达式计算 工具

分类:general | 标签:数学、计算、公式 安全求值数学表达式

2.1 功能说明

安全求值数学表达式

2.1.1 使用指南

功能说明

安全计算数学表达式,支持 + - * / ^ %、括号、常量 pi/e,函数 sqrt/sin/cos/tan/log/ln/abs/pow/fact。

使用场景

公式求值、作业核对(不使用 eval,防注入)。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • tokenize()
  • toRPN()
  • evalRPN()
  • run()
  • clear()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { ref } from 'vue'
import ToolPageShell from '@/components/ToolPageShell.vue'

const expr = ref('')
const result = ref('')
const error = ref('')

const FUNCS = {
  sqrt: Math.sqrt, sin: Math.sin, cos: Math.cos, tan: Math.tan,
  log: (x) => Math.log10(x), ln: Math.log, abs: Math.abs,
  pow: (a, b) => Math.pow(a, b), fact: (x) => { let r = 1; for (let i = 2; i <= x; i++) r *= i; return r }
}
const CONST = { pi: Math.PI, e: Math.E }

function tokenize(s) {
  const re = /\d+\.?\d*|[a-zA-Z_]+|\*\*|[%+\-*/^(),!]/g
  return s.match(re) || []
}
function toRPN(tokens) {
  const out = [], op = []
  const prec = { '+': 1, '-': 1, '*': 2, '/': 2, '%': 2, '^': 3, 'u-': 4, '!': 5 }
  const right = { '^': true, 'u-': true, '!': true }
  for (let i = 0; i < tokens.length; i++) {
    const t = tokens[i]
    if (/^\d+\.?\d*$/.test(t)) out.push({ n: parseFloat(t) })
    else if (CONST[t] !== undefined) out.push({ n: CONST[t] })
    else if (FUNCS[t]) { op.push(t) }
    else if (t === '(') op.push(t)
    else if (t === ')') {
      while (op.length && op[op.length - 1] !== '(') out.push({ o: op.pop() })
      if (!op.length) throw new Error('括号不匹配')
      op.pop()
    } else if (t === '!') out.push({ o: '!' })
    else if (t === '-' && (i === 0 || ['+', '-', '*', '/', '%', '^', '('].includes(tokens[i - 1]) || FUNCS[tokens[i - 1]])) {
      op.push('u-')
    } else if (t === '^' || t === '*' || t === '/' || t === '%' || t === '+' || t === '-') {
      while (op.length && op[op.length - 1] !== '(' &&
        (prec[op[op.length - 1]] > prec[t] || (prec[op[op.length - 1]] === prec[t] && !right[t]))) {
        out.push({ o: op.pop() })
      }
      op.push(t)
    } else throw new Error('无法识别:' + t)
  }
  while (op.length) {
    const o = op.pop()
    if (o === '(') throw new Error('括号不匹配')
    out.push({ o })
  }
  return out
}
function evalRPN(rpn) {
  const st = []
  for (const t of rpn) {
    if (t.n !== undefined) st.push(t.n)
    else if (t.o === 'u-') st.push(-st.pop())
    else if (t.o === '!') { const x = st.pop(); st.push(FUNCS.fact(x)) }
    else if (FUNCS[t.o]) {
      if (t.o === 'fact') continue
      const b = st.pop(), a = st.pop(); st.push(FUNCS[t.o](a, b))
    } else {
      const b = st.pop(), a = st.pop()
      st.push({ '+': a + b, '-': a - b, '*': a * b, '/': a / b, '%': a % b, '^': Math.pow(a, b) }[t.o])
    }
  }
  if (st.length !== 1) throw new Error('表达式无效')
  return st[0]
}
function run() {
  error.value = ''; result.value = ''
  const s = expr.value.trim()
  if (!s) { error.value = '请输入表达式'; return }
  try {
    const rpn = toRPN(tokenize(s.replace(/\*\*/g, '^')))
    const v = evalRPN(rpn)
    if (!Number.isFinite(v)) throw new Error('结果非有限数')
    result.value = String(v)
  } catch (e) { error.value = e.message }
}
function clear() { expr.value = result.value = error.value = '' }

2.3 效果截图

数学表达式计算 效果截图

工具访问地址:https://www.i91tools.com/tools/math-eval