- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
本文以仓库 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 快照。文件结构非常简单,只有两部分:
- 标题行:
# [euler.test.w] | test | sim,声明了被测文件名(链接回测试源码)、执行的操作(test)和目标平台(sim); 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:EULER | euler.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,可以手工还原计算过程:
math.E ** 0.05,即e^0.05 ≈ 1.051271096…;100 * 1.051271… ≈ 105.1271…;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 ⚡
相关推荐
Wing 语言 math 标准库 min/max 函数实战:从用法到 Hangar 快照测试验证
Wing 语言 math 标准库 min/max 函数实战:从用法到 Hangar 快照测试验证 导读 本文以 Wing 仓库中 math.min / math
编程语言编译器云原生语言运行时Wing 语言 math 标准库 factorial 函数深度解析:从 SDK 实现到 Hangar 快照测试验证
Wing 语言 math 标准库 factorial 函数深度解析:从 SDK 实现到 Hangar 快照测试验证 math.factorial 是 Wing
编程语言编译器云原生语言运行时Wing 语言 math.log10 对数计算实战:从标准库实现到 hangar 快照编译验证
Wing 语言 math.log10 对数计算实战:从标准库实现到 hangar 快照编译验证 math.log10 是 Wing 标准库 math 模块中用于
编程语言编译器云原生语言运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考