floating_vs_decmial_x API
パッケージ Luna-Flow/diff_bench/floating_vs_decmial_x は、同一の十進入力に対して moonbitlang/x/decimal(X)と Luna-Flow/floating/decimal_gda@0.7.1(GDA)を比較し、両者を BigInt オラクルで検査して Mare Mark で計測します。再現のために GitHub リポジトリに置かれたベンチマーク用ハーネスであり、他のプロジェクトの実行時依存ではありません。関数は契約外の入力で中断します。
パッケージ名は歴史的な綴り decmial のままです。チュートリアルでは各項目の使い方を示し、設計ページでは精度契約を導出します。ソース:src/floating_vs_decmial_x/。
例ではパッケージを @floating_vs_decmial_x として、また moonbitlang/core/bigint をインポートします。
中立な十進モデル
これらの項目の定義は dzmingli_vs_floating と同じです。各ベンチマークが単独でビルドできるよう、二つのパッケージは別々のコピーを持ちます。
DecimalValue
DecimalValue は値 を持つ、実装に依存しない十進数 です。
pub(all) struct DecimalValue {
coefficient : @bigint.BigInt
scale : Int
}
normalize
normalize はスケールが非負である範囲で係数の末尾の 0 を取り除きます。0 は になります。
pub fn normalize(DecimalValue) -> DecimalValue
canonical_string
canonical_string は正規化した値を指数なしの位取り記法で表します。等しい数は等しい文字列になります。
pub fn canonical_string(DecimalValue) -> String
parse_decimal_value
parse_decimal_value は符号、小数点、指数を省略可能に含む有限の十進文字列を正規化された値に読み込みます。それ以外では中断します。
pub fn parse_decimal_value(String) -> DecimalValue
OracleResult
OracleResult は正規化された値とその正準文字列を保持します。
pub struct OracleResult {
value : DecimalValue
canonical : String
}
to_oracle_result
to_oracle_result は値を正規化し、その正準文字列と組にします。
pub fn to_oracle_result(DecimalValue) -> OracleResult
test "neutral decimal model" {
let v = @floating_vs_decmial_x.parse_decimal_value("0.00120")
inspect(v.coefficient, content="12")
inspect(v.scale, content="4")
inspect(@floating_vs_decmial_x.canonical_string(v), content="0.0012")
let r = @floating_vs_decmial_x.to_oracle_result({ coefficient: -1200N, scale: 2 })
inspect(r.canonical, content="-12")
}
厳密な算術
add
add は両オペランドを大きい方のスケールに揃えたうえで厳密な和を返します。
pub fn add(DecimalValue, DecimalValue) -> DecimalValue
subtract
subtract は同じ揃え方の後で厳密な差を返します。
pub fn subtract(DecimalValue, DecimalValue) -> DecimalValue
multiply
multiply は厳密な積を返します。
pub fn multiply(DecimalValue, DecimalValue) -> DecimalValue
compare
compare は -1、0、1 のいずれかを返します。
pub fn compare(DecimalValue, DecimalValue) -> Int
test "exact arithmetic" {
let a = @floating_vs_decmial_x.parse_decimal_value("123.45")
let b = @floating_vs_decmial_x.parse_decimal_value("-4.5")
let show = @floating_vs_decmial_x.canonical_string
inspect(show(@floating_vs_decmial_x.add(a, b)), content="118.95")
inspect(show(@floating_vs_decmial_x.subtract(a, b)), content="127.95")
inspect(show(@floating_vs_decmial_x.multiply(a, b)), content="-555.525")
inspect(@floating_vs_decmial_x.compare(b, a), content="-1")
}
参照オラクル
オラクルは X の結果ポリシーを模倣します。小数部は最大 28 桁で、それを超える桁は 0 方向に切り捨てます。
oracle_multiply
oracle_multiply は厳密な積を返し、スケールが 28 を超えるときは小数 28 桁まで 0 方向に切り捨てます。
pub fn oracle_multiply(DecimalValue, DecimalValue) -> DecimalValue
oracle_divide
oracle_divide は小数 28 桁まで 0 方向に切り捨てた商を、整数除算だけで計算して返します。
pub fn oracle_divide(DecimalValue, DecimalValue) -> DecimalValue
とすると、結果の係数は のとき 、そうでなければ で、スケールは 28 です。除数が 0 なら中断します。dzmingli_vs_floating と異なり、循環小数になる商も扱えます。
oracle_operation
oracle_operation は上記の規則で Operation を評価します。Add、Subtract、Compare は厳密、Multiply と Divide は切り捨て、Parse と Format は恒等です。
pub fn oracle_operation(Operation, DecimalValue, DecimalValue) -> OracleResult
オラクルは DecimalSemantics に依存しません。一つのオラクルで両方のグループをまかなえる理由は設計ページにあります。
test "reference oracle" {
let one = @floating_vs_decmial_x.parse_decimal_value("1")
let three = @floating_vs_decmial_x.parse_decimal_value("3")
inspect(
@floating_vs_decmial_x.oracle_operation(Divide, one, three).canonical,
content="0.3333333333333333333333333333",
)
let tiny : @floating_vs_decmial_x.DecimalValue = { coefficient: 1N, scale: 20 }
let wide : @floating_vs_decmial_x.DecimalValue = { coefficient: 123456789N, scale: 10 }
inspect(
@floating_vs_decmial_x.canonical_string(@floating_vs_decmial_x.oracle_multiply(tiny, wide)),
content="0.0000000000000000000001234567",
)
}
演算、意味論、スイート
Operation
Operation はこのベンチマークの演算ファミリーを列挙します。
pub(all) enum Operation {
Add
Subtract
Multiply
Divide
Compare
Parse
Format
}
Parse と Format はどちらの側でも準備済みの左オペランドを返し、実行ファイルはこれらを実行しません。
operation_name
operation_name はレコードで使うスネークケースの名前("divide" など)を返します。
pub fn operation_name(Operation) -> String
DecimalSemantics
DecimalSemantics は両実装が満たすべき意味論的な契約を選びます。
pub(all) enum DecimalSemantics {
ExactOverlap
XCompatible
}
ExactOverlap は両ライブラリが厳密な結果を表現できる入力を使うので、どちらの側でも丸めは起きません。XCompatible は multiply と divide の後に quantize を行うことで X の小数 28 桁切り捨てを GDA で再現し、この処理は計時パスの内側にあります。
decimal_semantics_name
decimal_semantics_name は "exact_overlap" または "x_compatible" を返します。
pub fn decimal_semantics_name(DecimalSemantics) -> String
OperandShape
OperandShape は生成されたケースにラベルを付けます。ラベルはオペランドに影響しません。
pub(all) enum OperandShape {
Small
Large
Boundary
Cancellation
Repeating
}
TimingScope
TimingScope は、スイートのメタデータとして、計時される呼び出しに含まれる内容を表します。
pub(all) enum TimingScope {
ConstructionAndOperation
OperationOnly
}
Mare Mark ランナーはこれを読みません。比較レコードは代わりに文字列の範囲を持ちます。XCompatible での Multiply と Divide は "semantic_equivalent_pipeline"、それ以外は "arithmetic_only" です。
Suite
Suite はベンチマークスイートを記述するメタデータです。
pub struct Suite {
name : String
operations : Array[Operation]
scales : Array[Int]
timing_scope : TimingScope
warmup : Int
samples : Int
}
default_suite
default_suite はスイート "decimal-x-floating-gda" を返します。全 7 演算、スケール [0, 2, 6, 18, 28]、OperationOnly、ウォームアップ 5 回、サンプル 20 個です。
pub fn default_suite() -> Suite
suite_case_count
suite_case_count は 演算数 × スケール数 × 演算ごとのケース数 を返します。
pub fn suite_case_count(Suite, Int) -> Int
test "operations, semantics and suites" {
inspect(@floating_vs_decmial_x.operation_name(Divide), content="divide")
inspect(@floating_vs_decmial_x.decimal_semantics_name(XCompatible), content="x_compatible")
let suite = @floating_vs_decmial_x.default_suite()
inspect(@floating_vs_decmial_x.suite_case_count(suite, 2), content="70")
}
決定的なケース
generate_decimal
generate_decimal(seed, digits, scale, negative) は係数がちょうど digits 桁(各桁は )で、そのうち scale 桁が小数部である十進文字列を作ります。digits <= 0 または scale < 0 なら中断します。
pub fn generate_decimal(Int, Int, Int, Bool) -> String
BenchmarkCase
BenchmarkCase は生成されたケースで、その文字列がシリアライズの境界です。
pub struct BenchmarkCase {
id : String
operation : Operation
shape : OperandShape
left : String
right : String
scale : Int
}
generate_cases
generate_cases(seed, count, operation) は、桁数 、スケール の再現可能なケースを、時計やグローバルな乱数状態を使わずに count 個返します。
pub fn generate_cases(Int, Int, Operation) -> Array[BenchmarkCase]
serialize_case
serialize_case は decimal-neutral-v1 とケースの各フィールドを改行で連結します。
pub fn serialize_case(BenchmarkCase) -> String
fingerprint_case
fingerprint_case は Mare Mark の stable_fingerprint で serialize_case をハッシュします。
pub fn fingerprint_case(BenchmarkCase) -> String
expand_digit_scales
expand_digit_scales(sizes, n) は各係数サイズを n 回繰り返し、繰り返しごとに Mare Mark のデータセットを一つ作ります。
pub fn expand_digit_scales(Array[Int], Int) -> Array[Int]
test "deterministic cases" {
inspect(@floating_vs_decmial_x.generate_decimal(0, 2, 4, true), content="-0.0018")
let first = @floating_vs_decmial_x.generate_cases(17, 4, Multiply)
let again = @floating_vs_decmial_x.generate_cases(17, 4, Multiply)
inspect(first[3].right == again[3].right, content="true")
assert_eq(@floating_vs_decmial_x.expand_digit_scales([1, 4], 3), [1, 1, 1, 4, 4, 4])
}
フィクスチャとアダプター
working_precision
working_precision(operation, left, right, semantics?) はフィクスチャの GDA コンテキスト精度を返します。semantics の既定値は XCompatible です。
pub fn working_precision(Operation, DecimalValue, DecimalValue, semantics? : DecimalSemantics) -> Int
を係数の桁数、 をスケールとすると:
| 演算 | 精度 |
|---|---|
Add, Subtract | |
Multiply | |
Divide, ExactOverlap | |
Divide, XCompatible | |
Compare, Parse, Format |
各行とそれがカバーするオペランドクラスは設計ページで導出しています。
DecimalFixture
DecimalFixture は計時前に構築した両実装のオペランドを保持します。
pub struct DecimalFixture {
neutral_left : DecimalValue
neutral_right : DecimalValue
operation : Operation
semantics : DecimalSemantics
x_left : @decimal.Decimal
x_right : @decimal.Decimal
gda_left : @decimal_gda.Decimal
gda_right : @decimal_gda.Decimal
gda_quantum_28 : @decimal_gda.Decimal
gda_context : @decimal_gda.GdaContext
}
@decimal は moonbitlang/x/decimal です。gda_quantum_28 は で、XCompatible の後処理で使う量子です。
prepare_fixture
prepare_fixture(operation, left, right, semantics?) は精度 を計算し、両オペランドを X 用と GDA 用に変換し、精度 と 0 方向への丸めを持つ GDA コンテキストを作ります。
pub fn prepare_fixture(Operation, DecimalValue, DecimalValue, semantics? : DecimalSemantics) -> DecimalFixture
semantics の既定値は XCompatible です。GDA のオペランドは精度 と偶数丸めで解析されるため、有効桁数が を超えるオペランドは丸められます。既知の制限を参照してください。
x_from_neutral
x_from_neutral は Decimal::new で値を変換します。スケールが X の範囲 を外れると中断します。
pub fn x_from_neutral(DecimalValue) -> @decimal.Decimal
gda_from_neutral
gda_from_neutral(value, precision) は Decimal::from_string(precision~) で正準文字列を解析します。
pub fn gda_from_neutral(DecimalValue, Int) -> @decimal_gda.Decimal
DecimalObservation
DecimalObservation はアダプター呼び出し一回の生の結果です。
pub(all) enum DecimalObservation {
X(@decimal.Decimal)
XCompare(Int)
Gda(@decimal_gda.GdaOutcome[@decimal_gda.Decimal])
}
XCompare は -1、0、1 に正規化済みの X の比較結果を保持します。
run_x
run_x は X の演算子 +、-、*、/ または Compare::compare でフィクスチャの演算を実行します。
pub fn run_x(DecimalFixture) -> DecimalObservation
run_gda
run_gda は floating GDA でフィクスチャの演算を実行します。XCompatible では、スケールが 28 を超える積とすべての商を に量子化します。
pub fn run_gda(DecimalFixture) -> DecimalObservation
canonical_x
canonical_x は X の十進数を、その係数とスケールから正規化された DecimalValue に変換します。
pub fn canonical_x(@decimal.Decimal) -> DecimalValue
canonical_observation
canonical_observation は任意の観測値を正規化された DecimalValue に変換します。GDA の結果は to_string と parse_decimal_value を通ります。
pub fn canonical_observation(DecimalObservation) -> DecimalValue
test "fixtures and adapters" {
let one = @floating_vs_decmial_x.parse_decimal_value("1")
let three = @floating_vs_decmial_x.parse_decimal_value("3")
inspect(@floating_vs_decmial_x.working_precision(Divide, one, three), content="31")
let fixture = @floating_vs_decmial_x.prepare_fixture(Divide, one, three)
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")
}
Mare Mark との統合
MareDecimalInput
MareDecimalInput は一つの Mare Mark データセットの具体化された入力です。
pub(all) struct MareDecimalInput {
left : DecimalValue
right : DecimalValue
operation : Operation
semantics : DecimalSemantics
digits : Int
left_scale : Int
right_scale : Int
}
PerformanceResult
PerformanceResult は対応のある確認用サンプルから、一つの演算、意味論グループ、係数サイズの結果を要約します。
pub(all) struct PerformanceResult {
operation : Operation
semantics : DecimalSemantics
timing_scope : String
digits : Int
x_median_us : Double
gda_median_us : Double
gda_relative_delta_pct : Double
x_speedup_vs_gda : Double
decision : String
samples : Int
}
x_speedup_vs_gda は GDA の中央値を X の中央値で割った値です( を超えれば X が速い)。decision は "gda_faster"、"x_faster"、"equivalent"、"invalid"、"unknown" のいずれかです。dzmingli_vs_floating と異なり結果は常に対応付けられ、検証の失敗はレポートの件数にだけ現れます。
PerformanceResult::to_json
PerformanceResult::to_json は成果物バージョン mmka_1 の "comparison" JSONL レコードを出力します。
pub fn PerformanceResult::to_json(Self) -> String
MareBenchmarkReport
MareBenchmarkReport は一回の実行の結果、生の JSONL、検証件数をまとめます。validation_count は合格と不合格の両方の検証を数えます。
pub(all) struct MareBenchmarkReport {
results : Array[PerformanceResult]
jsonl : String
validation_count : Int
failed_count : Int
}
smoke_protocol
smoke_protocol は短いテスト用プロトコルを返します。ウォームアップ 2 回、0.5 ms の較正バッチ、確認用の繰り返し 3 回です。
pub fn smoke_protocol() -> @model.RunProtocol
default_benchmark_protocol
default_benchmark_protocol は公開された実行のプロトコルを返します。ウォームアップ 5 回、5 ms の較正バッチ、シード 0xDEC1A1 のバランスブロック、外れ値は報告のみ、確認用の繰り返し 20 回です。
pub fn default_benchmark_protocol() -> @model.RunProtocol
benchmark_environment
benchmark_environment は MARE_* 環境変数から環境スナップショットを作り、未設定のものには固定の既定値を使います。
pub fn benchmark_environment() -> @model.EnvironmentSnapshot
run_mare_benchmark
run_mare_benchmark(operations, semantics, digit_scales, protocol, seed) は一つの意味論グループのもとで演算ごとに Mare Mark の実験を実行し、まとめたレポートを返します。
pub async fn run_mare_benchmark(Array[Operation], DecimalSemantics, Array[Int], @model.RunProtocol, UInt64) -> MareBenchmarkReport
各データセットは計時前にオラクルで検証されます。この関数は失敗で中断しないので、呼び出し側が failed_count を確認します。native と js ターゲットで動きます。
async test "mare mark smoke run" {
let report = @floating_vs_decmial_x.run_mare_benchmark(
[Divide],
XCompatible,
[16],
@floating_vs_decmial_x.smoke_protocol(),
42UL,
)
inspect(report.failed_count, content="0")
inspect(report.results[0].timing_scope, content="semantic_equivalent_pipeline")
}
レポート
mare_performance_report_document
mare_performance_report_document(results, target, run_id) は Plot IR 文書を作ります。演算と意味論グループごとにレイテンシのプロットを一つ、グループごとに X 対 GDA の速度比プロットを一つ含みます。
pub fn mare_performance_report_document(Array[PerformanceResult], String, String, validation_count? : Int, failed_count? : Int) -> @ir_model.PlotDocument
コーパスの要約は、総数 validation_count + failed_count、合格 validation_count、不合格 failed_count です。validation_count はすでに失敗を含むので、要約が正しいのは failed_count が 0 のときだけです。設計ページを参照してください。
mare_performance_report_html
mare_performance_report_html は同じ文書を自己完結した HTML ページとして描画します。
pub fn mare_performance_report_html(Array[PerformanceResult], String, String, validation_count? : Int, failed_count? : Int) -> String
test "performance report" {
let result : @floating_vs_decmial_x.PerformanceResult = {
operation: Add,
semantics: ExactOverlap,
timing_scope: "arithmetic_only",
digits: 16,
x_median_us: 1.0,
gda_median_us: 2.0,
gda_relative_delta_pct: 100.0,
x_speedup_vs_gda: 2.0,
decision: "x_faster",
samples: 60,
}
let html = @floating_vs_decmial_x.mare_performance_report_html(
[result],
"native",
"example",
validation_count=2,
)
inspect(html.contains("x_decimal"), content="true")
}