当前位置: 首页 > news >正文

【OpenHarmony/HarmonyOs 】科学计算器实现细节:本地表达式解析、历史记录与零网络依赖

【OpenHarmony/HarmonyOs 】科学计算器实现细节:本地表达式解析、历史记录与零网络依赖

项目类型:OpenHarmony / HarmonyOS ArkTS 数学学习应用
项目名称:数学视界
对应主题:精细化权限管控、隐私保护方案、端侧 AI
关键词:ArkTS、科学计算器、表达式解析、本地计算、隐私保护、学习数据 🧮

一、为什么计算器也值得单独写一篇?

计算器看起来是一个很常见的功能,但在学习类 App 里,它不仅仅是“输入数字,得到结果”。一个好的数学计算器至少要考虑:

  • 运算符优先级;
  • 括号;
  • 幂运算;
  • 科学计数法;
  • 三角函数、对数、平方根;
  • 角度/弧度切换;
  • 计算历史;
  • 错误提示;
  • 深色模式可读性;
  • 不依赖网络、不上传表达式。

数学视界项目里的Calculator.ets做了一个很重要的选择:普通算术表达式没有直接用new Function求值,而是手写了解析器。这一点非常适合写成 CSDN 实战文章。

二、计算器页面的核心状态

计算器的状态主要包括显示值、表达式、角度模式、历史面板、记忆值等:

@Statedisplay: string ='0'@Stateexpression: string =''@StateisRadian: boolean = true@StateisSecond: boolean = false@StateshowHistory: boolean = false@StatememoryValue: number =0@StatelastResult: string ='0'@StateawaitingNthRoot: boolean = false@StatenthRootY: number =0@StateisDarkMode: boolean = false

这些状态对应不同功能:

  • display:屏幕主显示;
  • expression:实际参与计算的表达式;
  • isRadian:三角函数使用弧度还是角度;
  • isSecond:科学计算器第二功能键;
  • showHistory:是否打开历史记录;
  • memoryValue:M+、MR 等记忆功能;
  • awaitingNthRoot:处理 y 次根号这种两步输入。

三、输入处理:防止连续运算符

普通计算器最容易出现的问题是用户连续输入运算符,比如1++23*/4。项目中通过appendOp()做了拦截:

appendOp(op:string):void{constdisplayLast:string=this.display.slice(-1)constexprLast:string=this.expression.slice(-1)constopChars:string='+-*/%^'if(opChars.indexOf(displayLast) >=0|| opChars.indexOf(exprLast) >=0) {return}this.display += opthis.expression += op }

这个判断很简单,但用户体验会明显提升。错误表达式越早拦截,后面的解析器压力越小。

四、表达式解析器:不用 new Function 的本地求值

项目中定义了一个解析游标:

interfaceExprEvalState{ s:stringi: number }

它表示当前正在解析的字符串和位置。解析器按优先级拆成几层:

  • parseNumberSt():解析数字和科学计数法;
  • parsePrimarySt():解析数字或括号;
  • parseUnarySt():解析正负号;
  • parsePowerSt():解析幂运算;
  • parseTermSt():解析乘除取余;
  • parseExprSt():解析加减。

入口函数如下:

private evaluateArithmeticExpression(expr: string): number {conststate: ExprEvalState = { s: expr, i:0}constv: number = this.parseExprSt(state) this.skipWsSt(state) if (state.i< state.s.length) { throw new Error('extra') } return v }

这就是一个典型的递归下降解析器。它的好处是可控、安全、可扩展。

五、运算符优先级:从加减到幂运算

加减优先级最低:

private parseExprSt(state: ExprEvalState): number { let v: number = this.parseTermSt(state) while (true) { this.skipWsSt(state)constop: string =state.s[state.i] if (op === '+') {state.i++ v += this.parseTermSt(state) } else if (op === '-') {state.i++ v -= this.parseTermSt(state) } else { break } } return v }

乘除取余优先级更高:

private parseTermSt(state: ExprEvalState): number { let v: number = this.parsePowerSt(state) while (true) { this.skipWsSt(state)constop: string =state.s[state.i] if (op === '*') {state.i++ v *= this.parsePowerSt(state) } else if (op === '/') {state.i++ v /= this.parsePowerSt(state) } else if (op === '%') {state.i++ v %= this.parsePowerSt(state) } else { break } } return v }

幂运算使用右结合:

private parsePowerSt(state: ExprEvalState): number {constleft: number = this.parseUnarySt(state) this.skipWsSt(state) if (state.i +1< state.s.length && state.s[state.i] === '*' && state.s[state.i + 1] === '*') { state.i += 2 const right: number = this.parsePowerSt(state) return Math.pow(left, right) } return left }

右结合意味着2^3^2会按2^(3^2)理解,这更符合数学幂运算习惯。

六、计算流程:预处理、求值、记录历史

点击等号后会进入calculate()

calculate(): void {try{ let expr: string =this.expression.replace(/,/g,'')if(expr ===''|| expr.trim() ==='') {return} expr = expr.replace(/\^/g,'**') expr =this.preprocessExpression(expr)constresult: number =this.evaluateArithmeticExpression(expr)if(!isFinite(result)) {this.display ='Error'this.expression =''this.showToast('⚠️ 计算错误')return}this.finishCalculation(result) }catch{this.display ='Error'this.expression =''this.showToast('⚠️ 表达式错误') } }

这里有几个关键点:

  • 先去掉千分位逗号;
  • ^转换成**
  • 做函数名和常量预处理;
  • 调用本地解析器求值;
  • 捕获异常并显示错误提示。

七、科学函数预处理

项目支持一些常见科学函数:

preprocessExpression(expr:string):string{letresult:string= expr result = result.replace(/sin\(/g,'Math.sin(') result = result.replace(/cos\(/g,'Math.cos(') result = result.replace(/tan\(/g,'Math.tan(') result = result.replace(/sqrt\(/g,'Math.sqrt(') result = result.replace(/log\(/g,'Math.log10(') result = result.replace(/ln\(/g,'Math.log(') result = result.replace(/abs\(/g,'Math.abs(') result = result.replace(/exp\(/g,'Math.exp(') result = result.replace(/pi/g,Math.PI.toString()) result =this.replaceStandaloneE(result)returnresult }

这部分目前将函数名映射到了Math。后续如果要让解析器完全接管科学函数,可以把parsePrimarySt()扩展成支持函数调用,例如识别sin(...)sqrt(...),再在白名单中执行对应数学函数。

八、历史记录:计算也是学习行为

计算成功后,项目会写入历史记录:

finishCalculation(result:number, historyExpr?:string):void{constformatted:string=this.formatResult(result)constprevExpr:string= historyExpr !==undefined? historyExpr :this.expressionthis.lastResult= formattedif(AppState.calcHistory.length>=100) {AppState.calcHistory.pop() }AppState.calcHistory.unshift({id:Date.now().toString(),expression: prevExpr,result: formatted,time:newDate().toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'}),favorite:false, })this.display= formattedthis.expression= formattedAppState.recordCalculation() }

这里有两个设计点:

  1. 历史最多保留 100 条,避免无限增长;
  2. 每次计算会调用AppState.recordCalculation(),同步到学习统计。

也就是说,计算器不只是工具页,它也参与了整个数学学习闭环。

九、隐私角度:零网络依赖更适合学习工具

科学计算器完全可以接云端 AI,让它解释每一步。但对当前项目来说,本地计算更合适:

  • 🔐 表达式不上传;
  • ⚡ 计算即时完成;
  • 📶 没网也能用;
  • 🧒 更适合学生独立练习;
  • 🧩 可与本地历史、成就、今日目标联动。

这也是“精细化权限管控”的一种体现:不是所有智能都要接云端,不是所有计算都要请求权限。

十、总结

这篇文章和另一个项目里的“公式计算器”不同,重点在数学视界科学计算器的本地表达式解析。

核心实现包括:

  • 🧮 用displayexpression分离展示与计算;
  • ✋ 用appendOp()拦截连续运算符;
  • 🧠 用递归下降解析器处理优先级;
  • 🔢 支持加减乘除、取余、幂运算、括号和科学计数法;
  • 🧾 用calcHistory保存最近 100 条计算记录;
  • 🎯 用recordCalculation()接入学习统计;
  • 🔐 全程本地计算,零网络依赖。

对 OpenHarmony 数学学习 App 来说,这类本地计算能力非常值得打磨。它不花哨,但可靠、快速、隐私友好。✨

http://www.jsqmd.com/news/1125102/

相关文章:

  • WebAssembly 跨语言数据格式:JSON 方便,但不一定便宜
  • AI机器学习高级数学与优化
  • 豆包AI vs DeepSeek:生活可用性与技术能力的范式之辨
  • AI写教材必备攻略!掌握这些技巧,低查重生成高质量教材不是梦
  • SQL注入从原理到实战:手工注入、绕过技巧与安全防御详解
  • LangGraph add_conditional_edges 完整详解
  • 实战指南:快速掌握ForgeGradle的完整构建流程
  • 豆包、千问下线智能体:不是 Agent 凉了,是野蛮生长期结束了
  • DeepBump三分钟上手教程:从平面图片到三维纹理的魔法转换
  • 镜像视界纯视觉无感定位视频孪生底层技术全解
  • STM32F405RG驱动WS2812 LED的嵌入式开发实践
  • DyberPet:重新定义桌面交互的虚拟伙伴开发框架
  • 配置文件的工程化管理:从环境变量到结构化配置的演化路径
  • 网络安全渗透测试入门:从DVWA到在线靶场的实战训练指南
  • AI 电动窗帘电机智能功率 高可靠及 IoT 智能联动 核心选型方案
  • DockDoor终极指南:重新定义macOS窗口管理与效率革命
  • League-Toolkit:英雄联盟智能游戏助手的革命性突破
  • 探索 Aqua,Hyperliquid 如何打通衍生品流动性向零售渗透的最终圣杯
  • UI自动化测试中span元素定位的5种核心技巧与最佳实践
  • 四大核心视频孪生底层技术专题解析:拓扑图谱打通跨镜全域连续轨迹,分区并行实现超大实景实时重建;空间大模型驱动AI前置风险推演,SpaceOS底座统一四维孪生算力根基。四大技术体系原生耦合闭环,构筑
  • GPT5.5 辅助论文写作实践:选题生成、文献整理与摘要润色流程
  • CRITIC-TOPSIS算法改进与MATLAB实现:供应链决策优化
  • 微信单向好友检测终极指南:3步快速识别谁删除了你
  • Kimi、GLM5、M2.7实战选型指南:按业务场景选最稳的大模型
  • 486图片按序展示
  • Nginx安全防护与HTTPS部署实战:从系统加固到应用层防御
  • Dify实战:从零构建企业级AI应用,快速部署RAG问答机器人
  • 大模型学习路线:从理论到实践的完整指南
  • 告别Selenium弹窗噩梦:Playwright实现无头浏览器文件自动下载实战
  • 软件测试智能化升级与落地实践