dzmingli_vs_floating 教程
本教程说明如何对照精确预言机检查 DzmingLi/decimal 和 floating 的 decimal_gda——先是单个运算,再是确定性语料——如何在测试中运行一次小型 Mare Mark 测量,以及如何复现已发布的基准及其官方 decTest 审计。检查背后的数学见设计页。
快速开始
diff_bench 是仅在 GitHub 上的仓库,没有发布到 mooncakes。克隆它并在模块内工作,或把克隆加入一个 moon.work 工作区:
git clone https://github.com/Luna-Flow/diff_bench.git
cd diff_bench
moon test --target native
模块内的包在其 moon.pkg 中导入基准包:
import {
"Luna-Flow/diff_bench/dzmingli_vs_floating",
}
最小的有用程序对照预言机检查两个库中的一次除法:
test "quick start" {
let a = @dzmingli_vs_floating.parse_decimal_value("1.25")
let b = @dzmingli_vs_floating.parse_decimal_value("8")
let fixture = @dzmingli_vs_floating.prepare_fixture(Divide, a, b)
let expected = @dzmingli_vs_floating.oracle_operation(Divide, a, b).canonical
let show = (o : @dzmingli_vs_floating.DecimalObservation) => {
@dzmingli_vs_floating.canonical_string(@dzmingli_vs_floating.canonical_observation(o))
}
inspect(expected, content="0.15625")
inspect(show(@dzmingli_vs_floating.run_dz(fixture)), content="0.15625")
inspect(show(@dzmingli_vs_floating.run_gda(fixture)), content="0.15625")
}
三行 inspect 就是输出:预言机与两个库在 上一致。
日常任务
在一个输入上检查每个运算族
oracle_operation3 和 prepare_fixture3 为 Fma 接受第三个操作数;其他运算忽略它。下面的循环在适合全部运算的输入上检查 16 种运算:SquareRoot 用完全平方数,Power 用整数指数,ScaleB 用零移位。
test "every operation on one input" {
let ops : Array[@dzmingli_vs_floating.Operation] = [
Add, Subtract, Multiply, Divide, DivideInteger, Remainder, Power, Fma,
SquareRoot, Plus, Minus, Abs, Reduce, ToIntegralExact, ToIntegralValue,
Compare,
]
let left = @dzmingli_vs_floating.parse_decimal_value("144")
let two = @dzmingli_vs_floating.parse_decimal_value("2")
let third = @dzmingli_vs_floating.parse_decimal_value("0.5")
let mut agreed = 0
for op in ops {
let expected = @dzmingli_vs_floating.oracle_operation3(op, left, two, third).canonical
let fixture = @dzmingli_vs_floating.prepare_fixture3(op, left, two, third)
for observation in [
@dzmingli_vs_floating.run_dz(fixture),
@dzmingli_vs_floating.run_gda(fixture),
] {
let got = @dzmingli_vs_floating.canonical_observation(observation)
if @dzmingli_vs_floating.canonical_string(got) == expected {
agreed += 1
}
}
}
inspect(agreed, content="32")
}
全部 16 种运算在两个库中都一致()。
计时时包含或不包含解析
run_dz 和 run_gda 使用计时前解析好的操作数;run_dz_full 和 run_gda_full 先解析规范字符串。两条路径必须给出相同的值,FullPath 计时范围正依赖于此:
test "both timing paths agree" {
let a = @dzmingli_vs_floating.parse_decimal_value("123456789.000000018")
let b = @dzmingli_vs_floating.parse_decimal_value("-0.987654321")
let fixture = @dzmingli_vs_floating.prepare_fixture(Multiply, a, b)
let show = (o : @dzmingli_vs_floating.DecimalObservation) => {
@dzmingli_vs_floating.canonical_string(@dzmingli_vs_floating.canonical_observation(o))
}
let fast = show(@dzmingli_vs_floating.run_gda(fixture))
inspect(fast, content="-121932631.112635286777777778")
inspect(show(@dzmingli_vs_floating.run_gda_full(fixture)) == fast, content="true")
inspect(show(@dzmingli_vs_floating.run_dz_full(fixture)) == fast, content="true")
inspect(@dzmingli_vs_floating.oracle_operation(Multiply, a, b).canonical == fast, content="true")
}
校验确定性语料
generate_cases 对相同的种子给出相同的操作数。这样的语料中的除法可能不能整除为有限小数,所以循环只检查结果总是精确的运算:
test "deterministic corpus" {
let ops : Array[@dzmingli_vs_floating.Operation] = [Add, Subtract, Multiply, Compare]
let mut checked = 0
for op in ops {
for case in @dzmingli_vs_floating.generate_cases(73, 20, op) {
let left = @dzmingli_vs_floating.parse_decimal_value(case.left)
let right = @dzmingli_vs_floating.parse_decimal_value(case.right)
let expected = @dzmingli_vs_floating.oracle_operation(op, left, right).canonical
let fixture = @dzmingli_vs_floating.prepare_fixture(op, left, right)
let gda = @dzmingli_vs_floating.canonical_observation(@dzmingli_vs_floating.run_gda(fixture))
let dz = @dzmingli_vs_floating.canonical_observation(@dzmingli_vs_floating.run_dz(fixture))
assert_eq(@dzmingli_vs_floating.canonical_string(gda), expected)
assert_eq(@dzmingli_vs_floating.canonical_string(dz), expected)
checked += 1
}
}
inspect(checked, content="80")
}
看看精度不足是什么样子
夹具精度的选择保证没有结果被舍入。如果它太小,向零舍入会缩短结果,预言机比较就会失败。这里用 floating 的 decimal_gda 包(以 @decimal_gda 导入),在四位精度的上下文中计算一个有六位有效数字的积:
test "too little precision is detected" {
let a = @dzmingli_vs_floating.parse_decimal_value("123.45")
let b = @dzmingli_vs_floating.parse_decimal_value("6.7")
let context = @decimal_gda.context(precision=4, rounding=@decimal_gda.GdaRoundingMode::Down)
let product = @decimal_gda.multiply(
@dzmingli_vs_floating.gda_from_neutral(a, 8),
@dzmingli_vs_floating.gda_from_neutral(b, 8),
context,
)
let got = @dzmingli_vs_floating.canonical_observation(Gda(product))
inspect(@dzmingli_vs_floating.canonical_string(got), content="827.1")
inspect(@dzmingli_vs_floating.oracle_operation(Multiply, a, b).canonical, content="827.115")
inspect(@dzmingli_vs_floating.working_precision(Multiply, a, b), content="9")
}
working_precision 要求 9 位,足以容纳 的 6 位。
运行一次小型 Mare Mark 测量
run_mare_benchmark 先对照预言机校验每个数据集,然后对两个库计时。它是 async 函数,因此要在 native 或 js 目标上从 async test 中调用。smoke_protocol 让运行保持简短:
async test "smoke measurement" {
let report = @dzmingli_vs_floating.run_mare_benchmark(
[Add, Multiply],
FullPath,
@dzmingli_vs_floating.expand_digit_scales([16], 2),
@dzmingli_vs_floating.smoke_protocol(),
7UL,
)
inspect(report.failed_count, content="0")
inspect(report.validation_count, content="8")
inspect(report.results.length(), content="2")
inspect(report.results[0].samples, content="6")
}
两种运算 × 两个数据集 × 两个实现,得到八次校验。每个结果行配对 2 个数据集 × 3 次冒烟重复 = 6 个样本;其延迟为 report.results[i].dz_median_us 和 gda_median_us。
复现已发布的基准
在仓库根目录运行:
moon run --release src/dzmingli_vs_floating/bench --target native \
| sed -n '/^{/p' > artifacts/dzmingli_vs_floating/scaling.jsonl
moon run --release src/dzmingli_vs_floating/bench_common --target native \
| sed -n '/^{/p' > artifacts/dzmingli_vs_floating/common_digits.jsonl
规模扩展运行耗时很长,并在写完完整报告后以非零状态退出,因为 DzmingLi 从 4,096 位起校验失败。在读取数字之前,确认每条 Mare Mark summary 记录都写着 "complete":true,并设置 MARE_CPU、MARE_OS 和 MARE_BUILD_MODE 以记录主机。bench 页描述了输出。
官方 GDA decTest 审计单独运行:
sh tools/run_dzmingli_dectest_audit.sh
它下载 decTest 归档,检查其 SHA-256,对两个库运行共享的运算文件;只要 DzmingLi 已知的 toSci 失败仍然存在,它就以非零状态退出。
进阶
你自己的操作数类别。 prepare_fixture3 根据操作数选择精度。精度约定只对生成的类别得到证明;对新输入,请检查精确结果是否放得下。如果放不下,校验会失败而不是悄悄通过,因此一个失败的新夹具首先可能说明夹具有问题,而不是库有问题。
新增运算。 给 Operation 添加构造器,在 operation_name 中加入其名称,在 oracle_operation3 中加入规则,在 working_precision 中加入一行精度(或在 prepare_fixture3 中覆盖),在两个适配器中加入调用,并在 Mare Mark 实例化器中加入输入规则。在计时之前,添加一个在边界值上对照预言机检查新运算的测试。
图表。 tools/ 中的 Python 布局读取 JSONL,构建 Mare Mark Plot IR,并用 Matplotlib 渲染 PNG、PDF 和 SVG:
python3 tools/plot_dzmingli_benchmark.py \
artifacts/dzmingli_vs_floating/scaling.jsonl \
--output artifacts/dzmingli_vs_floating/main \
--ir-output artifacts/dzmingli_vs_floating/main.ir.json
tools/plot_dzmingli_supplementary_benchmark.py 以同样方式绘制补充图。
兄弟包。 floating_vs_decmial_x 把同样的方法应用于 moonbitlang/x/decimal。这些库来自 floating,运行器来自 mare_mark。
常见陷阱
oracle_divide遇到循环小数商(例如 )时中止。只使用约分后分母是若干 和 之积的除数。oracle_operation传入的加数为零,因此对Fma是错误的;请用oracle_operation3。- 比较忽略指数和标志:GDA 的
1.20与 DzmingLi 的1.2一致。表示形式和条件请用 decTest 审计检查。 Parse与Format在两边都返回预先准备好的操作数;它们并不测量解析或格式化。validation_count包含失败的校验;通过的校验数是validation_count - failed_count。run_mare_benchmark不会因校验失败而中止;请检查failed_count。可执行程序则会在写完报告后中止。- 可执行程序只在
--target native下做实际工作;在其他目标上它们打印一条消息后退出。 - 不要在
wasm-gc上用BigInt::from_string构造很长的测试输入:在当前的moonbitlang/core中,它对数千位的字符串返回错误的值(4,096 个 9 被解析为一个 4,094 位的数)。parse_decimal_value自己累加数字,不受影响。包内测试division precision covers exact terminating quotients使用了from_string,但只断言一个下界,因此在所有目标上都能通过。 - DzmingLi 在乘积超过约 21,475 位时中止(见设计页);乘法输入请保持在 10,000 位以内。