floating_vs_decmial_x 教程

本教程说明如何在两个语义组下分别比较 moonbitlang/x/decimal(X)与 floating 的 decimal_gda(GDA),对照预言机检查它们,运行一次小型 Mare Mark 测量,以及复现已发布的基准。为什么这样选择分组和精度见设计页。

快速开始

本仓库没有发布到 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/floating_vs_decmial_x",
}

最小的有用程序在两个库中按 X 的策略计算 11 除以 33:

test "quick start" {
  let one = @floating_vs_decmial_x.parse_decimal_value("1")
  let three = @floating_vs_decmial_x.parse_decimal_value("3")
  let fixture = @floating_vs_decmial_x.prepare_fixture(Divide, one, three, semantics=XCompatible)
  let show = (o : @floating_vs_decmial_x.DecimalObservation) => {
    @floating_vs_decmial_x.canonical_string(@floating_vs_decmial_x.canonical_observation(o))
  }
  inspect(show(@floating_vs_decmial_x.run_x(fixture)), content="0.3333333333333333333333333333")
  inspect(show(@floating_vs_decmial_x.run_gda(fixture)), content="0.3333333333333333333333333333")
}

两者都返回截断到 28 位小数的商;GDA 通过一次 divide 加一次 quantize 得到它。

日常任务

选择语义组

ExactOverlap 适用于两个库都能表示其精确结果的输入。此时 GDA 只运行一次运算,并且必须返回精确值:

test "exact overlap" {
  let a = @floating_vs_decmial_x.parse_decimal_value("12345.6789")
  let b = @floating_vs_decmial_x.parse_decimal_value("8")
  let fixture = @floating_vs_decmial_x.prepare_fixture(Divide, a, b, semantics=ExactOverlap)
  let gda = @floating_vs_decmial_x.canonical_observation(@floating_vs_decmial_x.run_gda(fixture))
  inspect(@floating_vs_decmial_x.canonical_string(gda), content="1543.2098625")
  inspect(@floating_vs_decmial_x.working_precision(Divide, a, b, semantics=ExactOverlap), content="12")
}

XCompatible 重现 X 的 28 位截断。一个有 36 位小数的积在两边都被截到 28 位:

test "x-compatible product" {
  let a : @floating_vs_decmial_x.DecimalValue = { coefficient: 123456789N, scale: 18 }
  let b : @floating_vs_decmial_x.DecimalValue = { coefficient: 987654321N, scale: 18 }
  let fixture = @floating_vs_decmial_x.prepare_fixture(Multiply, a, b, semantics=XCompatible)
  let show = (o : @floating_vs_decmial_x.DecimalObservation) => {
    @floating_vs_decmial_x.canonical_string(@floating_vs_decmial_x.canonical_observation(o))
  }
  inspect(show(@floating_vs_decmial_x.run_x(fixture)), content="0.0000000000000000001219326311")
  inspect(show(@floating_vs_decmial_x.run_gda(fixture)), content="0.0000000000000000001219326311")
}

精确积为 1.21932631112635269×10−191.21932631112635269 \times 10^{-19};截断保留到 10−2810^{-28} 为止的各位。

对照预言机检查语料

oracle_operation 实现了 X 的策略,因此它是两个组的参考:

test "corpus against the oracle" {
  let ops : Array[@floating_vs_decmial_x.Operation] = [Add, Subtract, Multiply, Divide, Compare]
  let mut checked = 0
  for op in ops {
    for case in @floating_vs_decmial_x.generate_cases(73, 20, op) {
      let left = @floating_vs_decmial_x.parse_decimal_value(case.left)
      let right = @floating_vs_decmial_x.parse_decimal_value(case.right)
      let expected = @floating_vs_decmial_x.oracle_operation(op, left, right).canonical
      let fixture = @floating_vs_decmial_x.prepare_fixture(op, left, right)
      for observation in [
        @floating_vs_decmial_x.run_x(fixture),
        @floating_vs_decmial_x.run_gda(fixture),
      ] {
        let got = @floating_vs_decmial_x.canonical_observation(observation)
        assert_eq(@floating_vs_decmial_x.canonical_string(got), expected)
      }
      checked += 1
    }
  }
  inspect(checked, content="100")
}

生成的用例至多 24 位,低于每个除法精度,因此操作数舍入限制不适用于它们。

运行一次小型 Mare Mark 测量

async test "smoke measurement" {
  let report = @floating_vs_decmial_x.run_mare_benchmark(
    [Add, Multiply],
    ExactOverlap,
    [4, 16],
    @floating_vs_decmial_x.smoke_protocol(),
    42UL,
  )
  inspect(report.failed_count, content="0")
  inspect(report.validation_count, content="8")
  inspect(report.results[0].timing_scope, content="arithmetic_only")
}

在 native 或 js 上运行它;该函数是 async 的。每个结果行包含以微秒计的 X 与 GDA 中位数以及 x_speedup_vs_gda,即 GDA 中位数除以 X 中位数。

复现已发布的基准

在仓库根目录执行:

moon run --release src/floating_vs_decmial_x/bench --target native \
  > artifacts/floating_vs_decmial_x/scaling.jsonl
moon run --release src/floating_vs_decmial_x/bench_common --target native \
  > artifacts/floating_vs_decmial_x/common_digits.jsonl

第一条命令测量 1 到 4,096 位,第二条测量 1、4、8、16、18 和 28 位。两者都在 JSONL 旁边写出一份 HTML 报告(见 bench 页)。设置 MARE_CPU、MARE_OS、MARE_BUILD_MODE 及其他 MARE_* 变量以记录主机;未设置的事实写作 unknown 或 unspecified。然后渲染图:

python3 tools/layout_x_decimal.py

按计时范围阅读记录:arithmetic_only 是一次公开运算,semantic_equivalent_pipeline 是在 GDA 中重现 X 策略的完整序列。来自不同目标的结果从不合并。

进阶

你自己输入的精度。 对 XCompatible 除法,working_precision 取决于操作数的位数差,而不是它们的长度。长于该精度的输入会在构造夹具时被舍入(见设计页)。需要保证一致时,让两个操作数都至多 pp 位。

解读加速比。 x_speedup_vs_gda 是配对样本的中位数之比;decision 对配对差的中位数应用 3 % 的实际阈值。两者都不是置信区间。兄弟基准的统计一节给出了公式。

兄弟包。 dzmingli_vs_floating 以精确预言机、带解析的第二个计时范围和 19 种运算应用相同的方法。

常见陷阱

  • prepare_fixture 和 working_precision 默认使用 XCompatible。需要精确组时请显式传入 semantics=ExactOverlap。
  • 在 XCompatible 除法中,有效位数多于精度的操作数会在 GDA 做除法之前被舍入:GDA 于是可能在 X 返回 0.49999999999999999999999999990.4999999999999999999999999999 时返回 0.50.5,计时比较的也是较短的 GDA 操作数和完整的 X 操作数。
  • x_from_neutral 在标度大于 28 时中止;X 无法保存这样的值。
  • HTML 汇总行只在没有失败的运行中才正确;请改为从报告中读取 validation_count 和 failed_count。
  • 在当前的 moonbitlang/core 中,BigInt::from_string 在 wasm-gc 上对长字符串给出错误结果。测试 division precision follows the requested semantic contract 用它构造一个由 4,096 个 9 组成的操作数,并期望 4097——这是那次错误解析产生的值;正确的工作精度是 4096+1+2=40994096 + 1 + 2 = 4099。因此该测试在 wasm-gc 上通过,在 native 和 js 上失败。请改用 parse_decimal_value 或 BigInt 算术构造长测试值。
  • X 的已发布数字是用 moonbitlang/x@0.4.46 测得的;模块现在依赖 0.5.5,而 JSONL 的实现记录仍写着 0.4.46。

下一步