gda_expr チュートリアル

このチュートリアルでは、MoonBit コードから General Decimal Arithmetic のテスト行(.decTest ファイル)を decimal_gda に対して実行する方法を説明します。文書の構文解析、実行、要約の読み取り、行が失敗した理由の調査を扱います。また、行の選択と実行のシャード分割も説明します。コマンドラインからコーパス全体を実行するには、代わりに gda_expr_cli ランナーを使ってください。

クイックスタート

moon.pkg にパッケージを追加します。

import {
  "Luna-Flow/floating/frontend/gda_expr",
}

2 つの行を構文解析して実行します。

///|
test "quick start" {
  let source =
    #|precision: 9
    #|rounding: half_even
    #|add001 add 1.20 2 -> 3.20
    #|div001 divide 1 3 -> 0.333333333 Inexact Rounded
    #|
  let document = @gda_expr.parse_dectest("quick.decTest", source).unwrap()
  let summary = @gda_expr.execute_documents([document])
  inspect(summary.passed_cases(), content="2")
  inspect(summary.success(), content="true")
}

行は id operation operand… -> expected condition… の形式をとります。行の上にあるディレクティブ(precision:、rounding:、…)は、後続のすべての行が使うコンテキストを設定します。

日常的なタスク

行が失敗した理由を読む

行が合格するのは、結果とコンディション(ステータスフラグ)の集合の両方が正確に一致する場合だけです。各 CaseResult は失敗時のメッセージを保持します。

///|
test "inspect failures" {
  let source =
    #|precision: 9
    #|ok1 multiply 3 4 -> 12
    #|bad1 add 1 1 -> 3
    #|bad2 divide 1 3 -> 0.333333333
    #|
  let document = @gda_expr.parse_dectest("failures.decTest", source).unwrap()
  let summary = @gda_expr.execute_documents([document])
  inspect(summary.failed_cases(), content="2")
  let messages = summary
    .results()
    .filter(r => !r.passed())
    .map(r => r.message())
  inspect(messages[0], content="result mismatch: expected 3, actual 2")
  inspect(
    messages[1],
    content="status flags mismatch: expected , actual Inexact Rounded",
  )
}

bad2 は桁は正しいものの Inexact Rounded が欠けています。フラグが余分な場合も、欠けている場合と同様に失敗です。

結果は指数も含めて比較される

期待される結果は、数値としてだけでなく 10 進の表現として比較されます。2.0 と 2.00 は値は同じですが指数が異なり、正しい指数を持つ方だけが合格します。符号付きゼロと NaN のペイロードも同様に比較されます。

///|
test "exponent matters" {
  let source =
    #|precision: 9
    #|q1 add 1.00 1.0 -> 2.00
    #|q2 add 1.00 1.0 -> 2.0
    #|z1 multiply -1 0 -> -0
    #|z2 multiply -1 0 -> 0
    #|
  let document = @gda_expr.parse_dectest("cohorts.decTest", source).unwrap()
  let passed = @gda_expr.execute_documents([document])
    .results()
    .map(r => r.id() + "=" + r.passed().to_string())
  inspect(passed.join(" "), content="q1=true q2=false z1=true z2=false")
}

スキップされる行と処置(disposition)

実行されない行もあります。各結果は CaseDisposition を持ちます。Executable の行は実行されます。Diagnostic の行(# のプレースホルダのオペランドまたは結果、あるいは ? のオペランドを含むもの)と Unsupported の行(未知の演算、コンディション、丸めモードを含むもの)はスキップとして数えられ、実行を失敗させることはありません。

///|
test "dispositions" {
  let source =
    #|precision: 9
    #|r1 add 1 1 -> 2
    #|r2 add # 1 -> #
    #|r3 frobnicate 1 -> 1
    #|rounding: banker
    #|r4 add 1 1 -> 2
    #|
  let document = @gda_expr.parse_dectest("mixed.decTest", source).unwrap()
  let summary = @gda_expr.execute_documents([document])
  inspect(summary.executable_cases(), content="1")
  inspect(summary.diagnostic_cases(), content="1")
  inspect(summary.unsupported_cases(), content="2")
  inspect(summary.skipped_cases(), content="3")
  inspect(summary.success(), content="true")
  let reasons = summary
    .results()
    .filter_map(r => {
      match r.disposition() {
        @gda_expr.Unsupported(reason) => Some(r.id() + ": " + reason)
        _ => None
      }
    })
  inspect(
    reasons.join("; "),
    content="r3: unsupported operation frobnicate; r4: unsupported rounding banker",
  )
}

success() は、実行可能な行が 1 つも失敗しなかったことしか示しません。strict なランナーはさらに、サポート外のものが何もないことも要求します。CLI では --strict-supported でこれを行います。

行を選択し、作業をシャードに分割する

RunOptions は ID で行を絞り込み、残った行のうち 1 つのシャードを選択します。フィルタは ID または範囲 first..last のカンマ区切りリストで、範囲は同じ長さでソート順が両端の間にある ID にマッチします。

///|
test "filter and shard" {
  let source =
    #|precision: 9
    #|add001 add 1 1 -> 2
    #|add002 add 2 2 -> 4
    #|add003 add 3 3 -> 6
    #|add004 add 4 4 -> 8
    #|mul001 multiply 2 3 -> 6
    #|
  let document = @gda_expr.parse_dectest("select.decTest", source).unwrap()
  let filtered = @gda_expr.execute_documents(
    [document],
    options=@gda_expr.RunOptions::new(case_filter="add002..add004,mul001"),
  )
  inspect(filtered.total_cases(), content="4")
  let shards = [0, 1, 2].map(index => {
    @gda_expr.execute_documents(
      [document],
      options=@gda_expr.RunOptions::new(shard_count=3, shard_index=index),
    )
  })
  inspect(shards.map(s => s.selected_cases().to_string()).join(" "), content="2 2 1")
  let merged = @gda_expr.RunSummary::merge(shards)
  inspect(merged.selected_cases(), content="5")
  inspect(merged.passed_cases(), content="5")
}

n 個中のシャード i は、絞り込み後の行における位置が n を法として i と合同である行を取ります。そのため各シャードは互いに素で、合わせるとすべての行を覆います。RunSummary::merge で件数を合算できます。

構文解析された行を見る

parse_dectest は各行のテキストとその行に適用されるコンテキストを保持するため、コーパスを実行せずに調べることができます。

///|
test "parsed rows" {
  let source =
    #|precision: 16
    #|rounding: ceiling
    #|maxExponent: 384
    #|minExponent: -383
    #|c1 add '1.5' "2" -> 3.5 -- quotes are optional
    #|
  let document = @gda_expr.parse_dectest("context.decTest", source).unwrap()
  let row = document.cases()[0]
  inspect(row.operation(), content="add")
  inspect(row.operands().join(" "), content="1.5 2")
  inspect(row.context().precision(), content="16")
  inspect(row.context().rounding(), content="ceiling")
  inspect(row.context().max_exponent(), content="384")
  inspect(row.span().line(), content="5")
}

さらに進んで

  • 交換形式の行。 精度と指数の限界がちょうど decimal32、decimal64、decimal128 のものであるコンテキストでは、# の後に 16 進数字が続く形で書かれたオペランドと結果は IEEE 754 の交換形式のエンコーディングであり、結果はビット単位で比較されます。32#、64#、128# を前置したオペランドはその形式で読み込まれます。
  • 期待値 ?。 結果が ? の場合は「任意の値」を意味し、コンディションだけが比較されます。
  • 独自の実行器。 すべての GdaCase は numeric_expr の木である expression() を公開しているため、同じ行を独自のコールバックで評価できます。例えば別の 10 進ライブラリに対して評価することもできます。
  • コーパス全体。 just conformance run decimal_gda は固定された公式コーパスをダウンロードし、native ランナーをビルドしてすべてのフェーズを実行します。検証 を参照してください。

よくある落とし穴

  • 構文解析エラーは文書全体を拒否します。 いずれかの行が不正な場合、parse_dectest はすべての診断を返し、文書は返しません。その行を修正するか削除してください。
  • 既定のコンテキストは decTest の既定値ではありません。 ディレクティブが現れる前のコンテキストは、精度 34、half_even、指数 ±999999999\pm 999999999、extended: 1、clamp: 0 です。実際のファイルは独自のディレクティブを設定します。
  • success() はスキップされた行を無視します。 サポートされない行で実行を失敗させる必要がある場合は、unsupported_cases() を確認してください(または CLI の --strict-supported を使います)。
  • 二重の引用符。 トークナイザは引用されたトークンを次の対応する引用符で終了させます。decTest 形式の '' エスケープは 2 つのトークンとして読まれます。

次のステップ