はじめに

このガイドでは、空の MoonBit プロジェクトから Luna-Flow/floating で正しい最初の結果を得るまでを案内します。パッケージの選択、インストール、値の構築、精度と失敗の報告方法の選択、そして結果の読み方を扱います。すべての例は現在のブランチに対してコンパイルされます。

数値ドメインを選ぶ

パッケージは、入力の表記ではなく、プログラムが必要とする意味論によって選んでください。

必要なものパッケージ主な型結果
任意精度の二進値、IEEE 754 二進形式bin_floatBinFloat値、または BinaryContext のもとでの (value, BinaryFlags)
IEEE 754 十進算術と decimal32/64/128 交換形式decimalDecimal値、または DecimalContext のもとでの (value, DecimalFlags)
スティッキーなステータスとトラップを備えた General Decimal Arithmeticdecimal_gdaDecimal次の GdaContext を伴う GdaOutcome[Decimal]
実数の結果の認証済み包含区間(IEEE 1788)ball_floatBallFloat, BallFloatDecorated区間、または BallContext のもとでの (interval, BallFlags)
最初のエラーで停止する二進パイプラインbin_float_checkedBinFloatResultラッパー内の Result[BinFloat, ArithmeticError]
フラグを蓄積する IEEE 十進パイプラインdecimal_checkedDecimalChecked定義済みの値と、最新および蓄積された DecimalFlags
トラップで停止する GDA パイプラインdecimal_gda_checkedGdaDecimalChecked受け渡される一つの GdaOutcome[Decimal]
最初のエラーで停止する区間パイプラインball_float_checkedBallFloatResultラッパー内の Result[BallFloat, ArithmeticError]
パッケージをまたいだ値の比較semanticSemanticScalar, SemanticIntervalメタデータを意図的に捨てた厳密な有理数

def は、これらすべてが共有する小さな語彙(Sign、PartialOrder、Floating トレイト、再エクスポートされた arithmetic の型)を保持します。numeric_expr と frontend/* はパーサと適合性検証ツールのためのものです。internal/*、cli/*、consistency、doc_examples、bench/* はリポジトリの基盤であり、アプリケーションの依存先ではありません。すべてのパッケージはマニュアルの概要に一覧があります。

インストールとインポート

MoonBit ツールチェーン 0.10 以降(moonc ≥ 0.10)が必要です。モジュールを追加し、丸めモードやコンテキストを自分で名指しする場合は Luna-Flow/arithmetic も追加します。

moon add Luna-Flow/floating@0.8.0
moon add Luna-Flow/arithmetic

moon.pkg では使用するパッケージだけをインポートします。

import {
  "Luna-Flow/arithmetic" @lf_arith,
  "Luna-Flow/floating/bin_float",
  "Luna-Flow/floating/decimal",
  "Luna-Flow/floating/ball_float",
}

インポートはファイルではなくパッケージを指定します。moon.pkg を持つ各ディレクトリが一つのパッケージであり、そのファイルは一つの名前空間を共有します。慣例として、このマニュアルでは Luna-Flow/arithmetic を @lf_arith、Luna-Flow/luna-generic を @lf_alg としてインポートします。

値を構築する

BinFloat は精度を持つ厳密な二進有理数 c⋅2ec \cdot 2^e です。Decimal は c⋅10qc \cdot 10^q で、リテラルの量子 qq を記憶します。BallFloat は二進の端点を持つ区間です。

///|
test "first values" {
  // 3 * 2^-1 at 53 bits of precision.
  let binary = @bin_float.BinFloat::make(
    @bin_float.BinCoeff::from_uint64(3UL),
    -1,
    53,
  )
  inspect(binary, content="3p-1")
  inspect(binary.to_shortest_string(), content="1.5")
  // Parsing keeps significant trailing zeros.
  let price = @decimal.Decimal::from_string("12.3400").unwrap()
  inspect(price, content="12.3400")
  inspect(price.quantum(), content="-4")
  // Every real number from 1 through 2.
  let interval = @ball_float.BallFloat::from_bounds(
    @bin_float.BinFloat::from_int(1),
    @bin_float.BinFloat::from_int(2),
  )
  inspect(interval.contains(binary), content="true")
}

BinFloat は厳密な形式 <coefficient>p<exponent> で出力されます。十進テキストには to_shortest_string または to_decimal_string_ctx を使用してください。十進数に対して normalized() を呼ぶのは、そのコホートを捨てたい場合だけにしてください。

その他のコンストラクタ: BinFloat::from_int、from_double、from_string と from_string_ctx(正しく丸められた十進パース)、from_hex。Decimal::from_int、from_string、from_string_ctx。BallFloat::from_int、from_double、exact、from_bounds。コンストラクタはオプションの precision を取ります。BallFloat::from_int のデフォルトは 16 ビットなので、binary64 相当の端点が必要な場合は precision=53 を渡してください。

コンテキストを選ぶ

通常の演算子(+、-、*、/ および add、mul、…)は、オペランドの精度で最近接偶数丸めを行い、実質的に無制限の指数範囲で動作し、ステータスを破棄します。精度、指数範囲、丸め方向、極小性、ステータスフラグが結果の一部となる場合は、明示的なコンテキストとともに *_ctx 形式を使用してください。

///|
test "contextual arithmetic" {
  let ctx = @bin_float.BinaryContext::binary64()
  let (third, flags) = @bin_float.BinFloat::from_int(1).div_ctx(
    @bin_float.BinFloat::from_int(3),
    ctx,
  )
  inspect(third.to_shortest_string(), content="0.3333333333333333")
  inspect(flags.inexact(), content="true")
  // The same quotient rounded upward lands one ulp higher.
  let up = @bin_float.BinaryContext::binary64(rounding=RoundTowardPositive)
  let (high, _) = @bin_float.BinFloat::from_int(1).div_ctx(
    @bin_float.BinFloat::from_int(3),
    up,
  )
  inspect(high.sub(third) == third.ulp(), content="true")
  let decimal_ctx = @decimal.DecimalContext::decimal64()
  let (q, decimal_flags) = @decimal.Decimal::from_int(1).div_ctx(
    @decimal.Decimal::from_int(3),
    decimal_ctx,
  )
  inspect(q, content="0.3333333333333333")
  inspect(decimal_flags.contains(@decimal.Inexact), content="true")
}

コンテキストは不変の値であり、ライブラリ内でグローバルな丸めモードを読むものはありません。IEEE のコンテキストは一つの演算のフラグを返し、それを自分で combine します。一方、GDA のコンテキストは結果とともに受け渡されます。すべての GdaOutcome は次のコンテキストを返し、そのステータスにフラグが蓄積されます。

失敗モデルを選ぶ

ライブラリは意図的に複数の失敗チャネルを公開しています。呼び出し側が観測すべき内容に合うものを選んでください。

  • Decimal::from_string のような単純なコンストラクタからの Option。不正な入力に診断情報が不要な場合に使います。
  • checked 演算(div_checked、sqrt、compare_checked、from_string、try_*_ctx 初等関数)からの Result[T, ArithmeticError]。
  • 定義済みの結果に付随する BinaryFlags、DecimalFlags、BallFlags。
  • トラップが発火しても GDA で定義された結果を保持する GdaOutcome[T]。
  • パイプラインのラッパー: BinFloatResult と BallFloatResult は最初のエラーで停止します。DecimalChecked は定義済みの NaN と無限大の結果を保持し、フラグを蓄積します。GdaDecimalChecked はトラップで停止し、その結果を保持します。
  • Empty、Entire、NaI。これらはエラーではなく区間の値です。
///|
test "pipelines" {
  let failed = @bin_float_checked.BinFloatResult::from_int(-4).sqrt()
  inspect(failed.is_err(), content="true")
  let total = @decimal_checked.DecimalChecked::parse(
      "1",
      @decimal.DecimalContext::decimal64(),
    )
    .div(@decimal.Decimal::from_int(3))
    .mul(@decimal.Decimal::from_int(3))
  inspect(total.value(), content="0.9999999999999999")
  // Accumulated over the pipeline versus raised by the last step.
  inspect(total.flags().contains(@decimal.Inexact), content="true")
  inspect(total.raised().contains(@decimal.Inexact), content="false")
}

これらのチャネルを一つの例外型や一つの Result にまとめないでください。標準が観測可能にしている意味論が失われます。

結果を正しく読む

  • 符号付きゼロ、無限大、quiet NaN と signaling NaN、NaN のペイロードはスカラーで観測可能です。
  • compare、<、ソートは全前順序を用います。そこではすべての NaN が互いに等しく、すべての数より上にあり、−0=+0-0 = +0 です。IEEE の意味論が必要な場合は、compare_checked、bin_float の quiet / signaling 述語、または total_order* 関数を使用してください。
  • BinFloat と BallFloat の == は表現を比較します(ゼロの符号と精度を含む)。Decimal の == は値を比較します。二進値の数値的な等価性には compare(a, b) == 0 を使用してください。
  • BallFloat にはスカラーの順序ではなく、包含関係と集合の関係(contains、subset、definitely_lt、…)があります。結果が正しいとは、それが厳密な結果を包含することです。区間の狭さは別の品質です。
  • SemanticScalar はパッケージをまたいで数学的な値を比較し、精度、量子、符号付きゼロ、ペイロード、装飾、フラグを捨てます。

次に読むもの