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 のポリシーに従って を で割ります:
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")
}
厳密な積は で、切り捨てによって の位までの桁が残ります。
コーパスをオラクルで検査する
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 のマイクロ秒単位の中央値と、GDA の中央値を X の中央値で割った x_speedup_vs_gda があります。
公開されたベンチマークを再現する
リポジトリのルートで:
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 はオペランドの長さではなく桁数の差で決まります。その精度より長い入力はフィクスチャの構築時に丸められます(設計ページを参照)。一致を保証したいときは両オペランドを 桁以下にしてください。
速度比の解釈。 x_speedup_vs_gda は対応サンプルの中央値の比で、decision は対応差の中央値に 3 % の実用上のしきい値を適用します。どちらも信頼区間ではありません。式は兄弟ベンチマークの統計の節にあります。
兄弟パッケージ。 dzmingli_vs_floating は厳密なオラクル、解析を含む第二の計時範囲、19 演算で同じ手法を適用します。
よくある落とし穴
prepare_fixtureとworking_precisionの既定値はXCompatibleです。厳密なグループを使いたいときはsemantics=ExactOverlapを明示してください。XCompatibleの除算では、有効桁数が精度より多いオペランドは GDA が割る前に丸められます。そのため X が を返すところで GDA が を返すことがあり、計時も短い GDA のオペランドと全長の X のオペランドを比べることになります。x_from_neutralはスケールが 28 を超えると中断します。X はそのような値を保持できません。- HTML の要約行が正しいのは失敗のない実行だけです。代わりにレポートの
validation_countとfailed_countを読んでください。 - 現在の
moonbitlang/coreでは、wasm-gcのBigInt::from_stringが長い文字列で誤った結果を返します。テストdivision precision follows the requested semantic contractはこれで 9 を 4,096 個並べたオペランドを作り、その誤った解析から生じた値4097を期待しています。正しい作業精度は です。そのためこのテストはwasm-gcでは通り、nativeとjsでは失敗します。長いテスト値はparse_decimal_valueかBigIntの算術で作ってください。 - X について公開された数値は
moonbitlang/x@0.4.46で計測されました。モジュールは現在0.5.5に依存していますが、JSONL の実装レコードにはまだ0.4.46と書かれています。
次のステップ
- API リファレンス:すべての項目。
- 設計:意味論グループと精度の証明。
- 性能分析:計測結果。
- ベンチマーク用実行ファイルと一般的な桁数用実行ファイル。