☰
Wing 语言 math 标准库欧拉常数 E 实战:从 euler 测试到 hangar 模拟器快照
2026/10/15 15:28:11 网站建设 项目流程
  • 编程语言
  • 编译器
  • 云原生
  • 语言运行时

【免费下载链接】wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡

项目地址:https://gitcode.com/gh_mirrors/wi/wing
点击查看免费下载

本文以仓库 tests/sdk_tests/math/euler.test.w 及其在 hangar 测试快照 中的模拟器(sim)运行记录为线索,完整解读 Wing 语言math标准库中欧拉常数math.E、math.round精度控制、inflight 闭包中的数值运算,以及 hangar 测试框架如何将wing test -t sim的输出固化为可对比的快照文件。读完本文,你将掌握math.E的取值与使用场景、decimalPlaces舍入的底层实现,以及 Wing 官方测试工具链的"测试 → 快照"完整链路。

快照文件解读:一次通过的模拟器测试记录

关联文档 euler.test.w_test_sim.md 是 hangar 框架为euler.test.w在sim目标(simulator 平台)下运行test命令后生成的 Markdown 快照。文件结构非常简单,只有两部分:

  1. 标题行:# [euler.test.w] | test | sim,声明了被测文件名(链接回测试源码)、执行的操作(test)和目标平台(sim);
  2. stdout.log代码块:记录了wing test -t sim输出的标准输出内容。
pass ─ euler.test.wsim » root/Default/test:EULER Tests 1 passed (1) Snapshots 1 skipped Test Files 1 passed (1) Duration <DURATION>

逐行含义如下:

输出行含义
pass ─ euler.test.wsim » root/Default/test:EULEReuler.test.w编译为euler.test.wsim模拟器应用后,测试运行器执行了应用树中root/Default/test:EULER节点下的测试(即源码中test "EULER" { ... }定义的测试),结果为pass
Tests 1 passed (1)共 1 个测试全部通过
Snapshots 1 skipped测试运行器自身的快照机制未启用/跳过(此处与 hangar 的 Markdown 快照无关)
Test Files 1 passed (1)1 个测试文件通过
Duration <DURATION>占位符,见下文sanitizeOutput的说明

关键点在于Duration <DURATION>:这并非运行时的真实耗时,而是 hangar 在生成快照前对输出做"脱敏"(sanitize)处理后的结果,保证快照内容与机器、时间、环境无关,从而可以被稳定地纳入版本控制做回归对比。

测试源码逐行解析:math.E 与复利计算

快照对应的测试源码位于 tests/sdk_tests/math/euler.test.w,全文仅 14 行,却覆盖了 Wing 数学模块的多个核心用法:

bring math; let interest = 0.05; let value = 100; let compoundOneYear = inflight (interestRate: num, currentVal: num): num => { return currentVal * (math.E ** interestRate); }; assert(math.round(math.E, decimalPlaces: 3) == 2.718); test "EULER" { assert(math.round(compoundOneYear(interest, value), decimalPlaces: 2) == 105.13); }

引入模块与常量定义

bring math;引入 Wing 标准库的math模块(实现位于 packages/@winglang/sdk/src/math/math.ts,通过 packages/@winglang/sdk/src/math/index.ts 统一导出)。随后定义了两个普通(preflight 期)数值变量interest = 0.05与value = 100,分别表示年利率 5% 与本金 100。

inflight 闭包中的指数运算

compoundOneYear是一个以inflight关键字声明的闭包,接收interestRate与currentVal两个num参数,返回currentVal * (math.E ** interestRate)——即按连续复利公式P * e^(r)计算一年后的金额。这里使用了两个关键元素:

  • math.E常量:欧拉数,源码中定义为public static readonly E = Math.E,即 JavaScript 运行时内置的Math.E(约 2.718281828459045);
  • **幂运算符:Wing 原生支持base ** exponent指数语法,这里math.E ** interestRate等价于数学上的e^r。

在 Wing 中,inflight表示该闭包在云/模拟器的运行时环境中执行(而非编译期的基础设施代码),这意味着math.E这样的纯数值运算在 inflight 环境中同样可用——math模块的Util类由静态方法构成、无状态,天然适合在两种阶段被调用。

preflight 断言:验证 E 的精度

assert(math.round(math.E, decimalPlaces: 3) == 2.718);

这一行位于顶层作用域,属于 preflight(编译期)代码。它断言math.E保留 3 位小数后等于2.718,从常量精度上先"钉死"了欧拉数的值,再进入运行时测试。

inflight 测试:验证复利结果

test "EULER" { assert(math.round(compoundOneYear(interest, value), decimalPlaces: 2) == 105.13); }

test "EULER"定义了一个模拟器运行时测试(对应快照输出中的root/Default/test:EULER)。测试内部调用 inflight 闭包计算复利,再断言保留 2 位小数后的结果为105.13。

底层实现:E 常量与 round 的 decimalPlaces 精度控制

math模块的完整实现位于 packages/@winglang/sdk/src/math/math.ts 的Util类中。与本文主题直接相关的有两处:

四个数学常量

public static readonly PI = Math.PI; public static readonly TAU = 2 * Math.PI; public static readonly E = Math.E; public static readonly INF = Number.POSITIVE_INFINITY;
  • PI:圆周率;
  • TAU:2 * PI(一周的弧度);
  • E:欧拉数,约 2.71828;
  • INF:正无穷大。

这些常量均映射到 JavaScript 运行时的内建值,因此精度与宿主环境(模拟器或云运行时)的 IEEE 754 双精度浮点数一致。在 Wing 的 API 文档 docs/api/04-standard-library/math/api-reference.md 中,E的类型标注为num,描述为 "Euler's number, a mathematical constant approximately equal to 2.71828"。

round 与 RoundingOptions

export interface RoundingOptions { readonly decimalPlaces?: number; } public static round(value: number, options?: RoundingOptions): number { if (options && options.decimalPlaces) { const multiplier = Math.pow(10, options.decimalPlaces); return Math.round(value * multiplier) / multiplier; } return Math.round(value); }

round的核心逻辑一目了然:

  • 不传decimalPlaces时,直接调用Math.round(value),四舍五入到最接近的整数;
  • 传入decimalPlaces: n时,先计算10^n作为乘数,将数值放大n个数量级后四舍五入,再缩小回来。例如decimalPlaces: 3即Math.round(E * 1000) / 1000 = Math.round(2718.28...) / 1000 = 2.718。

注意实现细节:RoundingOptions.decimalPlaces是可选属性(Wing 侧对应math.round(value, decimalPlaces: n)的具名参数写法),round的返回值仍然是num(浮点数),而非经过字符串格式化的小数——因此测试中比较的是数值相等== 105.13,这依赖 IEEE 754 下10513 / 100的精确表示,在实际运行中成立。

其他可用函数一览

同一Util类还提供了完整的数学函数集,可在实战中按需使用:abs、floor、ceil、sqrt、isPrime、min、max、median、mode、arithmeticMean、geometricMean、harmonicMean、fibonacci、factorial、combinations、hypot、degreesToRadians、radiansToDegrees、log(自然对数)、log2、log10、sign、三角/反三角/正割余割余切系列(sin/cos/tan/asin/acos/atan/atan2/sec/csc/cot/asec/acsc/acot)、random与toRadix。其中log即为以 e 为底的自然对数,与math.E配套使用可完成对数-指数互逆运算。

数值验证:105.13 是怎么算出来的

测试断言math.round(compoundOneYear(0.05, 100), decimalPlaces: 2) == 105.13,可以手工还原计算过程:

  1. math.E ** 0.05,即e^0.05 ≈ 1.051271096…;
  2. 100 * 1.051271… ≈ 105.1271…;
  3. round(105.1271…, decimalPlaces: 2):Math.round(105.1271 * 100) / 100 = Math.round(10512.71) / 100 = 10513 / 100 = 105.13。

如果测试里缺少decimalPlaces参数,round会直接得到整数105,断言将失败——这正是RoundingOptions存在的意义:在数值运算结果需要与业务期望的精度对齐时(如金额、比率),必须显式指定小数位。

快照背后的工具链:hangar 如何生成这份文档

这份快照不是手写的,而是 Wing 仓库的端到端测试框架 hangar(tools/hangar)自动生成的。它的完整链路由三个脚本文件构成:

第一步:按目录批量生成测试用例

tools/hangar/src/generate_tests.ts 会递归扫描tests/sdk_tests目录下所有.w文件(源目录在 tools/hangar/src/paths.ts 中定义为sdkTestsDir = path.join(testDir, "sdk_tests")),为每个文件生成两个 vitest 用例:

test("wing compile -t tf-aws", async () => { ... }); test("wing test -t sim", async () => { ... });

也就是说,sdk_tests下的每个测试文件都会被验证两次:一次面向tf-aws平台做编译检查,一次面向sim平台做测试执行。生成测试时还会跳过target与node_modules目录。

第二步:执行 wing test 并收集输出

tools/hangar/src/generated_test_targets.ts 中的testTest函数是执行入口:

export async function testTest(sourceDir, wingFile, env?, skipMarkdownSnapshot = false) { const platforms = ["sim"]; const args = ["test"]; const testDir = join(tmpDir, `${wingFile}_sim`); // only entrypoint files have tests (for now) if (!isEntrypointFile(wingFile)) return; ... const out = await runWingCommand({ cwd: sourceDir, platforms, wingFile, args, expectFailure: false, env: { WING_HIDE_CONTAINER_LOGS: "1", ...env }, }); if (out.stderr) fileMap["stderr.log"] = out.stderr; if (out.stdout) fileMap["stdout.log"] = out.stdout; if (!skipMarkdownSnapshot) { await createMarkdownSnapshot(fileMap, absoluteWingPath, "test", "sim"); } }

要点包括:

  • 平台固定为["sim"],命令参数为["test"],最终调用的是 utils.ts 中的runWingCommand,它通过execa以wing --no-update-check --no-analytics test <file> -t sim的形式执行 CLI;
  • 只有入口文件(*.main.w、*.test.w、main.w等,见isEntrypointFile)才会被测试,euler.test.w符合.test.w规则;
  • 标准输出与标准错误被分别收集到fileMap,随后交给快照生成函数。

第三步:脱敏与快照落盘

createMarkdownSnapshot(同样位于 tools/hangar/src/utils.ts)负责拼装 Markdown 并调用 vitest 的toMatchFileSnapshot写入__snapshots__/test_corpus/<相对目录>/<文件名>_test_sim.md。快照路径结构sdk_tests/math/euler.test.w_test_sim.md正是由这里决定的:${wingFile}_${testCase}_${target}.md。

快照正文由三部分组成:

let md = `# ${wingFile}}/${wingFile}) | ${testCase} | ${target}\n\n`; for (const file of files) { const extension = extname(file).replace(".", ""); md += `## ${file}\n\`\`\`${extension}\n${fileMap[file]}\n\`\`\`\n\n`; }

标题行中的相对链接是相对快照文件位置计算出来的(relative(dirname(snapPath), testDir)),因此快照里写的是../../../../../../tests/sdk_tests/math/euler.test.w这样的回退路径;在仓库根目录视角下,它对应 tests/sdk_tests/math/euler.test.w。

而输出中的Duration <DURATION>则来自runWingCommand内部调用的sanitizeOutput脱敏函数,其中包含:

// Remove duration from test results .replace(/Duration \d+m[\d.]+s/g, "Duration <DURATION>")

这类脱敏规则还有:归一化换行与 Windows 反斜杠、抹掉绝对路径(<ABSOLUTE>)、替换时间戳(<TIMESTAMP>)、端口号(<PORT>)、IP 地址与模拟器状态文件路径等,目的是让快照在不同开发者机器、不同 CI 环境上逐字节一致,从而能够可靠地做回归对比。

如何运行

在仓库根目录执行以下命令即可复现这一流程(详见 tools/hangar/package.json 的 scripts):

# 重新生成所有测试用例文件(扫描 tests 目录下的 .w) pnpm --filter hangar run test:generate # 运行全部 hangar 测试(含 sim 测试与快照比对,--update 可更新快照) pnpm --filter hangar run test # 仅跑文档示例测试 pnpm --filter hangar run test:doc_examples

单独针对该测试,也可以直接使用 Wing CLI 手动验证:

wing test -t sim tests/sdk_tests/math/euler.test.w

这条命令正是 hangar 在testTest中执行的核心动作;对照快照中的stdout.log,本地运行会得到pass ─ euler.test.wsim » root/Default/test:EULER的同类输出(唯一差异是Duration后跟真实耗时,而快照中已被脱敏为<DURATION>)。

延伸验证:round 的精度边界

同一目录下的 tests/sdk_tests/math/floor_ceil_round.test.w 对math.E与math.round的组合做了更全面的覆盖,可作为理解精度的补充材料:

assert(math.round(math.E) == 3); // 默认取整 assert(math.round(math.E, decimalPlaces: 1) == 2.7); // 1 位小数 assert(math.round(math.E, decimalPlaces: 2) == 2.72); // 2 位小数 assert(math.round(math.E, decimalPlaces: 3) == 2.718); // 3 位小数(euler 测试同款断言) assert(math.round(math.E, decimalPlaces: 4) == 2.7183); assert(math.round(math.E, decimalPlaces: 5) == 2.71828);

此外它还在test "inflight floor/ceil/round"块中于 inflight 环境重复了同样的断言,佐证了math模块函数在 preflight 与 inflight 两阶段行为一致。结合 floor_ceil_round 测试 与 euler 测试 可以看到 Wing 标准库测试的典型组织方式:顶层 assert 覆盖编译期行为,test块覆盖模拟器运行时行为,而 hangar 快照则自动记录每次变更后的输出,守护数值计算与 CLI 行为不因重构而漂移。

小结

从一份 11 行的快照文档出发,可以完整还原 Wing 语言数学能力的一条验证链路:

  • 语言层:bring math;引入标准库,math.E提供欧拉常数,**提供指数运算,inflight闭包与test块承载运行时数值逻辑;
  • 实现层:math.ts 的Util类以 JS 内建Math为基底,round通过10^n缩放实现任意小数位舍入;
  • 工程层:hangar 工具链 自动扫描tests/sdk_tests下的.w文件、执行wing test -t sim、脱敏输出并固化 Markdown 快照,让每次代码变更都能被机器对比验证。

如果你想在自有 Wing 项目中复用这套能力,核心模式就是:用math.E与math.log处理指数/对数类业务(如连续复利、衰减模型),用round(value, decimalPlaces: n)对齐展示精度,再借助 hangar 的快照机制为数值行为建立自动化回归防线。

  • 编程语言
  • 编译器
  • 云原生
  • 语言运行时

【免费下载链接】wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡

项目地址:https://gitcode.com/gh_mirrors/wi/wing
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询