← xlucky的博客

移动软件开发-实验5

在鸿蒙系统实现计算器

文章目录
  1. 1. 实验目的
  2. 2. 页面设计思路
  3. 3. 项目结构
  4. 4. 实验过程
  5. 二、问题总结与体会
  6. 1. 遇到的问题及解决方法
  7. 2. 实验体会

一、实验内容

1. 实验目的

本实验根据实验5要求,使用 DevEco Studio 和 ArkTS 开发能够在 HarmonyOS 系统运行的计算器。首先通过 Hello World 工程验证开发环境和模拟器连接,再实现数字输入、四则运算、清空、退格等基础功能。在此基础上加入科学计算、函数绘图、历史记录和主题切换,完成具有个人设计风格的计算器应用。

通过实验,学习 ArkUI 声明式界面、组件状态管理、表达式解析、Canvas 绘图以及设备调试,并理解浮点数计算和数学函数定义域对程序结果的影响。

2. 页面设计思路

应用采用“标准、科学、绘图”三个模式,共用顶部导航与主题切换。各模式的功能分开组织,切换时保留当前输入与结果。界面使用浅绿色与深绿色主题,通过留白、统一圆角和颜色区分数字键、运算键与主要操作。

模式或功能主要内容设计目的
标准计算四则运算、小数、正负切换、百分号、清空、退格满足日常计算需求
科学计算三角函数、反三角函数、对数、开方、幂、阶乘、括号支持完整数学表达式
结果显示DEG/RAD、AUTO/SCI、Ans区分角度单位、显示方式,并复用上次结果
函数绘图双曲线、缩放、取点、参数滑块直观观察函数形状及参数变化
历史记录标准与科学模式分别保存最近30条记录便于回看计算过程
主题与布局深浅主题、结果分区、简洁按钮提高可读性,减少多余文字

3. 项目结构

miniprogram5/
├── 实验报告.md
├── README.md
└── Calculator/
    ├── AppScope/                         # 应用信息与公共资源
    ├── build-profile.json5               # 工程构建配置
    ├── oh-package.json5                  # 依赖配置
    ├── hvigor/                           # 构建工具配置
    ├── entry/src/main/
    │   ├── module.json5                  # 模块及入口声明
    │   ├── resources/                    # 字符串、颜色、图标等
    │   └── ets/
    │       ├── entryability/EntryAbility.ets
    │       ├── pages/Index.ets            # 标准界面、导航与主题
    │       ├── components/
    │       │   ├── ScientificPanel.ets    # 科学计算界面
    │       │   └── GraphPanel.ets         # 函数绘图界面
    │       └── utils/
    │           ├── CalculatorEngine.ts   # 标准计算状态机
    │           └── ExpressionMath.ts     # 解析、求值及曲线采样
    ├── tests/
    │   ├── calculator.test.cjs
    │   ├── expression.test.cjs
    │   └── device-smoke.ps1
    └── screenshots/                      # 开发过程中的设备截图

虽然仓库名为 miniprogram5,本实验实际是 HarmonyOS 原生应用,应在 DevEco Studio 中打开 Calculator 目录。

4. 实验过程

4.1 验证开发环境与工程入口

在 Windows 环境下使用 DevEco Studio 打开现有的 Calculator 工程,运行默认 Hello World 页面,确认编译、安装和模拟器运行流程正常。工程通过 EntryAbility 加载 pages/Index,应用包名为 com.example.calculator。

使用 HDC 检查设备连接:

& 'E:\app\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe' list targets

开发阶段曾检测到设备 127.0.0.1:5555,并通过 HDC 安装和启动应用。当前工程配置的目标 SDK 为 26.0.0,最低兼容版本为 6.1.1(24),设备类型为 Phone。工程未保存签名配置,后续若部署到需要签名的设备,应在本机 DevEco Studio 中配置签名。

4.2 实现标准计算与输入状态管理

标准模式使用 CalculatorEngine 保存当前输入、数字列表、运算符列表以及等待输入、完成计算、发生错误等状态。界面只负责传递按键并同步显示,从而将界面布局与计算规则分离。

private press(key: string): void {
  const record = this.engine.press(key);
  this.display = this.engine.current;
  this.expression = this.engine.expression;
  if (record !== '') {
    this.history = [record, ...this.history].slice(0, 30);
  }
}

求值时先合并乘除项,再计算加减,保证 2 + 3 × 4 得到 14。输入过程中限制重复小数点,连续点击不同运算符时替换上一运算符;退格既能删除数字,也能撤回待输入状态下的运算符。除零时显示错误,重新输入数字或点击 AC 可以恢复。

百分号的规则为“当前数字除以100”,因此 200 + 10% 的结果是 200.1。明确这一规则可以避免与部分商业计算器的百分比语义混淆。

4.3 扩展科学计算和表达式解析

科学计算使用独立的表达式输入区和五列键盘,支持括号、幂、三角函数、对数、平方根、阶乘以及 π、e 等常数。科学模式可以切换 DEG 与 RAD,并通过 Ans 引用上次计算结果。

为使运算规则可控,程序没有将输入字符串交给 eval 执行,而是先进行词法分析,再使用递归下降方法构造表达式树。语法层次为:

加减表达式 → 乘除表达式 → 一元正负号 → 幂与后缀运算 → 数字、常数、括号或函数

其中幂按右结合处理,因此 2^3^2 等价于 2^(3^2);一元负号的优先级低于幂,因此 -2^2 得到 -4。函数计算还检查定义域,例如负数开平方、非正数取对数和非整数阶乘都会给出提示。

科学计算的核心调用如下:

const options = new MathOptions();
options.degrees = this.degrees;
options.ans = this.answer;
const value = evaluateMath(new ExpressionParser(this.formula).parse(), options);
this.answer = value;
this.result = formatMath(value, this.scientific);

AUTO 模式按有效数字显示,SCI 模式使用科学计数法。两者改变的是显示格式,不是另行截断内部计算结果。

4.4 处理特殊角度的浮点残差

开发时发现,在 RAD 模式下计算 sin(π) 会显示约 1.22464679915e-16,而不是数学上的零。这是因为双精度浮点数不能精确表示 π,普通三角函数计算会留下微小残差。

不能简单地把所有小于某个阈值的数都设为零,否则 sin(1e-16) 这样的有效微小结果也会丢失。本项目保留数字的原始文本,并对能够用安全整数分数表示的角度分析其是否为 π/2 或90°的整数倍。确认象限后直接使用精确值:

const quadrant = exactQuadrant(node.children[0], options);
if (quadrant !== undefined) {
  if (node.name === 'sin') { return [0, 1, 0, -1][quadrant]; }
  if (node.name === 'cos') { return [1, 0, -1, 0][quadrant]; }
  if (quadrant % 2 !== 0) { throw new Error('tan 在此角度无定义'); }
  return 0;
}

这种处理直接作用于求值结果,因此 sin(π) * 1e16 也得到零。无法严格确认的角度仍按一般浮点数计算,不进行全局归零。该方法修复的是可识别的特殊角度,并不意味着应用具备任意精度或完整的符号计算能力。

4.5 使用 Canvas 绘制函数曲线

绘图模式与科学模式共用表达式解析器,允许输入包含 x 和参数 a 的函数。默认对比 sin(a*x) 与 cos(x),同时提供抛物线和反比例预设。用户可以调整参数 a、缩放视图或点击图面读取函数值。

最新代码中,两条坐标轴采用统一的屏幕比例,避免函数图形被横向或纵向拉伸:

const scale = width / (2 * this.radius);
this.yRadius = height / (2 * scale);
const px = (x: number): number => width / 2 + x * scale;
const py = (y: number): number => height / 2 - y * scale;

由画布宽度和 x 轴范围确定统一比例,再根据画布高度计算 y 轴范围。这样每个单位在两条轴上的屏幕长度相同,网格为正方形,缩放后比例也保持一致。

曲线采用数值采样绘制。程序检查采样点和中点的值,在变化剧烈的位置继续细分;遇到无定义或无法可靠连接的区间时断开绘制,避免将反比例函数渐近线两侧错误连成直线。参数滑块变化后重新采样,取点时则将触摸横坐标转换为数学坐标并计算函数值。

4.6 精简界面并保留模式状态

初版界面包含副标题、优先级说明和较多操作提示。后续将这些说明移入项目文档,界面只保留必要的单位、待更新状态和错误信息,并增大结果字号、统一圆角和间距。等号使用深绿色突出显示,运算符使用浅绿色区分,清空键用暖色文字提示。

三个模式的组件保留在同一个 Stack 中,通过 Visibility 控制显示,而不是每次切换时重新创建,因此可以保留各自的输入、历史和绘图参数。数据目前保存在内存中,应用重启或页面重建后会重置。

4.7 测试与运行结果

2026年9月20日整理报告时,重新执行了当前项目的数学测试,结果如下:

测试文件验证内容本次结果
calculator.test.cjs基础运算、优先级、小数、退格、错误恢复21项断言通过
expression.test.cjs科学计算、特殊角度、按键组合、定义域、函数采样和纵轴范围120项断言通过
合计数学逻辑自动验证141项断言通过

典型用例包括:

输入或操作预期结果
2 + 3 × 414
0.1 + 0.2显示 0.3
DEG:sin(30)+2^38.5
RAD:sin(π)精确返回 0
RAD:sin(1e-16)保留微小结果,不误置为零
5!120
sqrt(-1)提示实数平方根不能输入负数
1/x原点无定义,不跨渐近线连线

开发阶段已完成构建、安装及设备交互验证。报告整理时 HDC 未检测到已连接设备,因此本次没有重新执行设备操作脚本,也没有将旧截图当作最新版界面的重新验收证据。

以下为开发阶段保存的真实设备截图。第一张为基础计算版本,第二张为绘图精简版;后续代码又增加了分区样式与两轴等比例绘制,截图用于记录开发过程,最终实现以仓库代码为准。

开发阶段标准计算结果:2+3×4=14

图1 基础计算功能验证

开发阶段双曲线绘图界面

图2 双曲线、缩放与参数控制界面(等比例优化前)

二、问题总结与体会

1. 遇到的问题及解决方法

  1. 计算只按输入顺序执行,容易破坏优先级。 标准模式先处理乘除再处理加减;科学模式通过分层语法构造表达式树,并分别验证幂的右结合和负号的优先级。

  2. 自定义方法名与 ArkUI 内置属性重名。 初版按钮构建方法使用 key,编译时与组件基类属性冲突。将其改为 calculatorKey 后消除冲突,也让方法用途更加清晰。

  3. sin(π) 显示微小残差。 通过表达式结构识别能够精确证明的特殊角度,再返回零或正负一;保留普通微小数值,避免全局阈值归零造成新的错误。

  4. 曲线在无定义点附近出现错误连接。 采样时检查函数定义域与中点偏差,对高曲率区间细分,无法可靠连接时断线。绘图本质上仍是数值近似,对极窄结构和高频振荡存在采样限制。

  5. 画布宽高不同导致坐标轴比例不一致。 改为共享同一个缩放系数,并由实际画布尺寸计算 y 轴范围,使单位长度一致,避免图形失真。

  6. 切换模式后输入状态容易丢失。 保留各模式组件实例,通过可见性切换页面,避免反复销毁和重建。

  7. 功能增加后界面文字过多。 删除常驻教程和重复标签,把详细规则保留在 README 中。界面优先展示公式、结果、曲线以及真正需要用户处理的状态。

2. 实验体会

本次实验从 Hello World 工程出发,逐步实现标准计算、科学计算和函数绘图,使我认识到计算器不仅是按钮布局,还涉及输入状态、数学语法、数值精度和交互反馈等多个问题。

将计算逻辑与界面分离后,可以在不启动模拟器的情况下验证大量数学用例;科学计算与绘图共用解析器,也减少了两种模式之间规则不一致的问题。浮点误差的处理则说明,显示结果看起来接近零,并不代表可以直接将其改为零,需要结合表达式语义判断。

后续可以继续加入历史持久化、图像导出和更多函数分析功能;对于本次实验,先保证现有功能的正确性、界面的清晰程度和测试的可重复性更为重要。