frontend/gda_expr の設計

設計目標

General Decimal Arithmetic 仕様11 M. F. Cowlishaw, General Decimal Arithmetic Specification, version 1.70, および付属の decTest スイート。IEEE 754-2019 は 10 進形式に同じ算術を採用しています。 には .decTest ファイルの大規模なコーパスが付属しています。各行は演算、オペランド、コンテキスト、厳密な期待結果、そして発生させなければならない条件の正確な集合を与えます。このパッケージはそのコーパスを、decimal_gda に関する実行可能で有限な主張に変えます。行が成功するのは、decimal_gda が同じ表現と同じ条件を生成した場合に限ります。パッケージは純粋(テキストを入力し、サマリを出力)であるため、プロセス内でテストでき、シャーディングでき、薄い CLI から駆動できます。

数学的背景

10 進データとコンテキスト

有限の GDA 数は、符号 s∈{0,1}s \in \{0, 1\}、整数の係数 c≥0c \ge 0、指数 qq からなる三つ組 (s,c,q)(s, c, q) で、値は (−1)s⋅c⋅10q(-1)^s \cdot c \cdot 10^{q} です。異なる三つ組が同じ値を持つことがあります。1 つの値の表現の集合がそのコホートであり、例えば 2.02.0 と 2.002.00 に対する (0,20,−1)(0, 20, -1) と (0,200,−2)(0, 200, -2) です。仕様は各演算がコホートのどの要素を返すか(理想指数)を定めているため、テスト行の期待結果は値だけでなく表現です。特殊値は ±∞\pm\infty と、整数のペイロードと符号を持つ quiet NaN または signaling NaN です。

コンテキスト κ=(p,ρ,Emin⁡,Emax⁡,clamp,extended)\kappa = (p, \rho, E_{\min}, E_{\max}, \mathit{clamp}, \mathit{extended}) は精度、丸めモード、指数範囲を与えます。演算 ff は厳密な結果を計算し、それを κ\kappa のもとで丸め、13 個の条件の部分集合を発生させます:Inexact、Rounded、Lost_digits、Invalid_operation、Division_by_zero、Overflow、Underflow、Subnormal、Clamped、Conversion_syntax、Division_impossible、Division_undefined、Invalid_context。

演算としての行

行

id  op  a1 … an  ->  x  c1 … cm

は、ディレクティブのコンテキスト κ\kappa のもとで numeric_expr の式 op(a1,…,an)\mathsf{op}(a_1, \dots, a_n) に変換され、2 つのコールバックで評価されます。リテラルのコールバックは各 aja_j を @decimal_gda.Decimal に復号します:

  • # に 16 進数字が続くもの:IEEE 754 の交換形式の符号化で、(p,Emin⁡,Emax⁡)(p, E_{\min}, E_{\max}) がコンテキストに等しい形式((7,−95,96)(7, -95, 96)、(16,−383,384)(16, -383, 384)、(34,−6143,6144)(34, -6143, 6144))で復号される。それ以外のコンテキストではオペランドは無効となる。
  • 32#…、64#…、128#…:その交換形式に丸められる 10 進テキスト。
  • それ以外は 10 進テキスト(先頭の + は取り除かれる)で、精度 max⁡(64,p)\max(64, p) で解析される。これにより、その桁数までのオペランドはすべて厳密に保たれる。

演算のコールバックは正規化された名前を 1 つの decimal_gda 関数に対応させ(例えば add を @decimal_gda.add に、squareroot を @decimal_gda.sqrt に、comparetotal を @decimal_gda.compare_total に)、κ\kappa からすべてのトラップを無効にして構築した @decimal_gda.GdaContext でそれを呼び出します。結果は 4 種類(10 進数、整数、真偽値、テキスト)のいずれかの値と、発生した条件の集合 FF です。変換 tosci と toeng は生のオペランドテキストを読みます。テキストからの変換自体がテスト対象だからです。交換形式専用の演算(canonical、および # オペランドと # 結果に対する apply、copy*)は符号化に対して作用し、テキストを返します。

合格規則

vv を実際の結果、FF を実際の条件集合、CC を列挙された条件の集合とします。CC が Division_impossible または Division_undefined を含むときに Invalid_operation を加えたものを C^\widehat{C} と定義します(どちらも無効演算のシグナルを通じて報告されます)。行が成功するのは次が成り立つとき、かつそのときに限ります

match⁡(v,x)  ∧  F=C^,\operatorname{match}(v, x) \;\wedge\; F = \widehat{C},

ここでフラグ集合の等価性は双方向に検査されます。条件の欠落も余分な条件も行を失敗させます。match⁡\operatorname{match} は期待されるトークン xx と vv の種類に依存します:

期待値 xx実際の値 vvmatch⁡(v,x)\operatorname{match}(v, x)
?任意真(条件のみを検査)
#hex10 進数コンテキストの形式での vv の交換形式の符号化が、大文字小文字を区別せずに xx と等しい
32#…, 64#…, 128#…10 進数vv がその形式での xx の値と数値的に等しい(compare == 0)。その形式で vv を符号化する際に発生した条件は FF に加えられる
その他のテキスト10 進数vv が xx とまったく同じに表示される、または compareTotal⁡(v,dec⁡(x))=0\operatorname{compareTotal}(v, \operatorname{dec}(x)) = 0
その他のテキスト整数vv の 10 進テキストが xx と等しい
その他のテキスト真偽値真に対しては xx が true/1、偽に対しては false/0(大文字小文字を区別しない)
テキストテキスト文字列が等しい(#hex では大文字小文字を区別しない)

10 進数の場合は表現について厳密です。IEEE 754 の totalOrder22 IEEE 754-2019, 5.10 節, totalOrder。Decimal::compare_total が decimal_gda 向けにこれを実装しています。 はまず符号、次にクラス、次に数値、次に指数を比較し、NaN は signaling ビットとペイロードで順序付けます。したがって

compareTotal⁡(a,b)=0  ⟺  {(sa,ca,qa)=(sb,cb,qb)finite,sa=sb±∞,sa=sb, same signaling bit and payloadNaN,\operatorname{compareTotal}(a, b) = 0 \iff \begin{cases} (s_a, c_a, q_a) = (s_b, c_b, q_b) & \text{finite,} \\ s_a = s_b & \pm\infty, \\ s_a = s_b,\ \text{same signaling bit and payload} & \text{NaN,} \end{cases}

よって 2.0 は 2.00 に、-0 は 0 に、NaN12 は NaN に一致しません。

設計上の判断

テストでない行には失敗ではなく処置を与える

問題。 公式コーパスにはスカラー計算を記述しない行が含まれます。無効または非スカラーの符号化を表す # プレースホルダ、旧バージョン由来の ? オペランド、ライブラリが提供しない演算、ライブラリが知らない丸めモードです。これらを失敗として数えると本当の失敗が埋もれ、黙って除外するとカバレッジを過大に見せてしまいます。

選択。 選択されたすべての行に処置が与えられます。Diagnostic は構造上実行できない行(# または ? のオペランド、# の結果)を示します。Unsupported はライブラリが実行できない正当な行(未知の演算、条件、丸め)を示します。成功または失敗しうるのは Executable の行だけであり、サマリはすべてのクラスを報告するため、「実行可能な行はすべて成功する」といった主張には、除外された行の数が伴います。厳格さ(何かが未対応なら失敗とすること)は呼び出し側のポリシーです。RunOptions がそれを記録し、CLI が終了コードに適用します。

トラップは無効化し、条件を比較する

.decTest の行はトラップではなく条件を列挙します。すべての演算を空のトラップ集合で実行すると、decimal_gda は仕様で定められた既定の結果(例えば無効演算に対する quiet NaN)を返し、発生したすべての条件を報告します。これはまさに行が述べている内容です。集合全体を双方向に比較することで、Inexact、Rounded、Clamped などの条件の欠落と余分な発生の両方を検出できます。

表現について厳密な比較

GDA はすべての結果の指数を規定しているため、正しい値を誤った指数で返すライブラリは誤りです。compare_total または完全な文字列一致で比較することで、コホート、ゼロの符号、NaN のペイロードがテストの対象になります。値レベルの比較は 32#/64#/128# の結果に対してのみであり、その場合に行がテストするのは符号化の条件です。

ディレクティブのコンテキストは変更ごとに一度だけ解決

各行は、その行で有効なディレクティブのレコードを保持します。execute_documents は、それが前の行のレコードと異なる場合にのみ decimal_gda のコンテキストに変換します。変換はレコードの純粋関数であるため、キャッシュが結果を変えることはありません。1 つのコンテキストのもとに数千行あるファイルで、繰り返しの作業を省きます。

決定的なシャーディング

問題。 コーパスは大きく並列プロセスで実行されますが、その結果を合算したものは逐次実行の結果と一致しなければなりません。

選択。 フィルタリング後、行には文書順に k=0,1,…,N−1k = 0, 1, \dots, N-1 と番号が付けられ、nn 個中のシャード ii は Si={k:k mod n=i}S_i = \{k : k \bmod n = i\} を受け持ちます。ラウンドロビン割り当てにより、遅い演算(power、ln)を含むファイルが、1 つのシャードにまとめて割り当てられることなく複数のシャードに分散されます。

正しさ/不変条件

分割。 n≥1n \ge 1 に対して、集合 S0,…,Sn−1S_0, \dots, S_{n-1} は互いに素であり {0,…,N−1}\{0, \dots, N-1\} を被覆します。各 kk は nn を法としてちょうど 1 つの剰余を持つからです。それらのサイズは均衡しています:

∣Si∣=⌈N−in⌉∈{⌊Nn⌋,⌈Nn⌉}.|S_i| = \left\lceil \frac{N - i}{n} \right\rceil \in \left\{ \left\lfloor \frac{N}{n} \right\rfloor, \left\lceil \frac{N}{n} \right\rceil \right\}.

シャードの独立性。 行の処置と結果は、その行(トークンとディレクティブのレコード)にのみ依存します。解析は行ごとにレコードを確定し、コンテキストのキャッシュは純粋関数をメモ化します。したがって行 kk の結果は、それを含むどのシャードでも逐次実行でも同じであり、

merge⁡(R0,…,Rn−1) has the counters of Rserial,\operatorname{merge}(R_0, \dots, R_{n-1}) \text{ has the counters of } R_{\text{serial}},

となります。merge はすべてのカウンタを加算し、シャードは行を分割するからです(total_cases はどのシャードでも同じ NN であり、merge はそれを保持します)。

カウンタの恒等式。 すべてのサマリについて selected=executable+skipped\text{selected} = \text{executable} + \text{skipped}、executable=passed+failed\text{executable} = \text{passed} + \text{failed}、skipped=diagnostic+legacy+unsupported\text{skipped} = \text{diagnostic} + \text{legacy} + \text{unsupported} が成り立ちます。internal/conformance によって各結果がちょうど 1 つのクラスに数えられるからです。

全域性。 解析はすべての行を、スキップ(空行またはコメント)、ディレクティブ、行、診断のちょうど 1 つに割り当てます。実行が行の内容によって中断することはありません。復号やディスパッチの失敗は、メッセージ "evaluation failed" を持つ失敗の結果になります。

計算量。 解析はテキスト長に対して線形です。実行は行数に対して線形であり、それに 10 進演算自体のコストが加わります。

却下した代替案

  • 値のみの比較。 より単純ですが、仕様とコーパスが意図的にテストしている誤った指数やゼロの符号を受け入れてしまいます。
  • 条件の部分集合比較(列挙された条件が発生すればよい)。余分な Inexact や Clamped を受け入れてしまいます。これはよくあるバグの類型です。
  • # と ? の行をその場しのぎの意味で実行する。 これらの行にはスカラーとしての意味がありません。意味をでっち上げると成功数が無意味になります。
  • 連続したシャード。 行リストをブロックに分割するのも同様に決定的ですが、遅いファイル全体が 1 つのシャードに入ってしまいます。

境界

  • ファイルシステムへのアクセス、グロブ展開、プロセスの終了コードは扱いません。それらは cli/gda_expr_cli と tools/ に属します。
  • トラップはありません。行はすべてのトラップを無効にして実行されます。
  • 有効桁数が max⁡(64,p)\max(64, p) を超えるオペランドは、復号時に最近接偶数丸めで丸められます。
  • 引用符付き decTest 文字列の '' エスケープは認識されません。
  • Legacy は共有の結果モデルの一部ですが、現在の実行器によって割り当てられることはありません。
  • この実行器は decimal_gda のみをテストします。IEEE 10 進(decimal)は tools/ に独自のコーパスランナーを持ちます。

Footnotes

  1. M. F. Cowlishaw, General Decimal Arithmetic Specification, version 1.70, および付属の decTest スイート。IEEE 754-2019 は 10 進形式に同じ算術を採用しています。 ↩

  2. IEEE 754-2019, 5.10 節, totalOrder。Decimal::compare_total が decimal_gda 向けにこれを実装しています。 ↩