def API

def は floating パッケージ群の共通語彙です。符号分類 Sign、4 通りの比較結果 PartialOrder、このリポジトリのすべてのスカラー表現および区間表現が実装するオープンな Floating トレイト、そのトレイト上に構築された 4 つのジェネリック述語を定義し、さらに Luna-Flow/arithmetic のコンテキスト、丸め、分類、エラーの各型を再エクスポートしているため、呼び出し側は 1 つのインポートだけで済みます。算術演算は一切含みません。チュートリアルではこれらの語彙の使い方を示し、設計ページではトレイトの法則を述べ、PartialOrder が全順序でない理由を説明しています。

このページの例はすべて、現在のブランチでコンパイルできる完全なテストです。パッケージは @def としてインポートされ、@lf_arith は Luna-Flow/arithmetic を指します。

符号と比較結果

Sign

Sign は Floating::sign が返す 3 通りの符号分類です。

pub(all) enum Sign {
  Negative
  Zero
  Positive
} derive(Eq)

Zero は符号付きゼロのどちらに対しても返されるため、Sign は −0-0 と +0+0 を区別しません。ゼロの符号ビットが重要な場合は、具体的なパッケージ(たとえば BinFloat::is_negative_zero)を使ってください。具体的な実装は NaN に対しても Zero を返し、区間は 00 を含むときに Zero を返します(Floating::sign を参照)。Sign は semantic パッケージの SemanticScalar::Infinity のペイロードでもあり、BinFloat::inf などのコンストラクタの引数でもあります。

///|
test "Sign values" {
  let s = @def.Floating::sign(@bin_float.BinFloat::from_int(-3))
  inspect(s == @def.Sign::Negative, content="true")
  let z = @def.Floating::sign(@bin_float.BinFloat::from_double(-0.0))
  inspect(z == @def.Sign::Zero, content="true")
}

PartialOrder

PartialOrder は IEEE 754 比較の結果です。2 つの浮動小数点データの間には、互いに排他的な 4 つの関係 less、equal、greater、unordered のうちちょうど 1 つが成り立ちます。

pub(all) enum PartialOrder {
  Less
  Equal
  Greater
  Unordered
} derive(Eq)

Unordered が成り立つのは、少なくとも一方のオペランドが NaN であるときに限ります。−0-0 と +0+0 は Equal と比較されます。この型は bin_float の BinFloat::compare_quiet と BinFloat::compare_signaling が返します(それぞれ比較のフラグと対になっています)。これは値型にすぎず、def 自身は比較を行いません。Compare インスタンスではなく 4 通りの結果にした理由は設計ページで導いています。

///|
test "PartialOrder from an IEEE comparison" {
  let one = @bin_float.BinFloat::from_int(1)
  let nan = @bin_float.BinFloat::nan()
  let (relation, flags) = one.compare_quiet(nan)
  inspect(relation == @def.PartialOrder::Unordered, content="true")
  inspect(flags.invalid_operation(), content="false")
  let (signaling, signaling_flags) = one.compare_signaling(nan)
  inspect(signaling == @def.PartialOrder::Unordered, content="true")
  inspect(signaling_flags.invalid_operation(), content="true")
  let zeros = @bin_float.BinFloat::from_double(-0.0).compare_quiet(
    @bin_float.BinFloat::zero(),
  )
  inspect(zeros.0 == @def.PartialOrder::Equal, content="true")
}

Floating トレイト

Floating は、このリポジトリのすべての表現が共有する、観測と精度変更のためのインターフェースです。

pub(open) trait Floating {
  fn classify(Self) -> @arithmetic.FpClass
  fn sign(Self) -> Sign
  fn precision(Self) -> Int
  fn with_precision(Self, Int, @arithmetic.RoundingMode) -> Self
  fn normalized(Self) -> Self
}

このトレイトは pub(open) なので、下流の型も実装できます。このリポジトリでは @bin_float.BinFloat、@decimal.Decimal、@decimal_gda.Decimal、@ball_float.BallFloat が実装しています。MoonBit 0.10 への移行以降、トレイトメソッドは自動的には昇格されないため、ジェネリックなコードでは @def.Floating::classify(x) という修飾形式で呼び出します。具体的な型は同名の固有メソッド(x.classify())も提供しています。すべての実装が満たすべき法則は設計ページに列挙されています。

Floating::classify

classify は値のクラス Finite、Infinity、NaN のいずれかを返します。

fn classify(Self) -> @arithmetic.FpClass

スカラー型では、signaling NaN と quiet NaN を統合した IEEE のクラスです。BallFloat では、有界で空でない区間なら Finite、無限大の端点を持つ区間(実数直線全体を含む)なら Infinity、空区間なら NaN です。全域的であり、決して中断しません。

Floating::sign

sign は値の符号分類を返します。

fn sign(Self) -> Sign
実装NegativeZeroPositive
BinFloat、両方の Decimal値が <0< 0(−∞-\infty を含む)±0\pm 0 およびすべての NaN値が >0> 0(+∞+\infty を含む)
BallFloat [ℓ,u][\ell, u]u<0u < 0ℓ≤0≤u\ell \le 0 \le uℓ>0\ell > 0

BallFloat::sign は符号を持たない空区間に対しては中断します。先に @def.is_nan(x)(空区間に対してのみ真)を調べてください。

Floating::precision

precision は値に保存されている作業精度を返します。BinFloat と BallFloat の端点では有効ビット数、両方の Decimal 型では有効 10 進桁数です。結果は常に 11 以上です。

Floating::with_precision

with_precision(x, p, mode) は x を精度 max⁡(1,p)\max(1, p) で表し直したものを返します。

fn with_precision(Self, Int, @arithmetic.RoundingMode) -> Self

有限のスカラーでは、値はその基数で max⁡(1,p)\max(1,p) 桁の有効桁に、指定された方向へ丸められます。指数範囲は無制限なので、オーバーフローもアンダーフローも起きません。フラグは報告されません(フラグが必要な場合は具体的なパッケージの *_ctx API を使ってください)。無限大と NaN はクラスを保ったまま、保存された精度だけが変わります。BallFloat では結果は入力の包含区間になります。中心は mode で丸められ、丸め誤差が半径に加えられるので、mode が何であっても x のすべての要素は結果の要素であり続けます。

Floating::normalized

normalized は値の数学的な値を変えずに、その正準な代表元を返します。

fn normalized(Self) -> Self

BinFloat では係数が奇数(またはゼロ)である表現です。両方の Decimal 型では係数の末尾のゼロを取り除くため、1.500 は 1.5 になります(コホートは変わりますが、値は変わりません)。有限でない値はそのまま返されます。normalized は冪等です。

///|
fn[F : @def.Floating] describe(x : F) -> String {
  let class = match @def.Floating::classify(x) {
    Finite => "finite"
    Infinity => "infinite"
    NaN => "nan"
  }
  let sign = match @def.Floating::sign(x) {
    Negative => "-"
    Zero => "0"
    Positive => "+"
  }
  "\{class} \{sign} precision=\{@def.Floating::precision(x)}"
}

///|
test "Floating observations" {
  inspect(
    describe(@bin_float.BinFloat::from_double(-0.0)),
    content="finite 0 precision=53",
  )
  inspect(
    describe(@decimal.Decimal::from_string("-1.50").unwrap()),
    content="finite - precision=34",
  )
  inspect(
    describe(@decimal_gda.Decimal::from_string("-Inf").unwrap()),
    content="infinite - precision=34",
  )
  let around_zero = @ball_float.BallFloat::from_bounds(
    @bin_float.BinFloat::from_int(-1),
    @bin_float.BinFloat::from_int(1),
  )
  inspect(describe(around_zero), content="finite 0 precision=53")
}

///|
test "Floating re-precision and normalization" {
  let d = @decimal.Decimal::from_string("2.71828").unwrap()
  let cut = @def.Floating::with_precision(d, 3, @lf_arith.RoundingMode::TowardZero)
  inspect(cut.to_string(), content="2.71")
  let ten = @def.Floating::with_precision(
    @bin_float.BinFloat::from_int(10),
    2,
    @lf_arith.RoundingMode::ToNearestEven,
  )
  inspect(ten.to_string(), content="1p3")
  inspect(ten.precision(), content="2")
  let cohort = @decimal.Decimal::from_string("1.500").unwrap()
  inspect(@def.Floating::normalized(cohort).to_string(), content="1.5")
}

ジェネリック述語

is_finite, is_infinite, is_nan, is_zero

これらの関数は任意の Floating 値のクラスを判定します。

pub fn[F : Floating] is_finite(F) -> Bool
pub fn[F : Floating] is_infinite(F) -> Bool
pub fn[F : Floating] is_nan(F) -> Bool
pub fn[F : Floating] is_zero(F) -> Bool
is_finite(x)  ⟺  classify(x)=Finite,is_infinite(x)  ⟺  classify(x)=Infinity,is_nan(x)  ⟺  classify(x)=NaN,is_zero(x)  ⟺  classify(x)=Finite∧sign(x)=Zero.\begin{aligned} \texttt{is\_finite}(x) &\iff \texttt{classify}(x) = \texttt{Finite},\\ \texttt{is\_infinite}(x) &\iff \texttt{classify}(x) = \texttt{Infinity},\\ \texttt{is\_nan}(x) &\iff \texttt{classify}(x) = \texttt{NaN},\\ \texttt{is\_zero}(x) &\iff \texttt{classify}(x) = \texttt{Finite} \wedge \texttt{sign}(x) = \texttt{Zero}. \end{aligned}

すべての値について、最初の 3 つのうちちょうど 1 つが真になります。スカラーでは、is_zero は符号付きゼロのどちらに対しても真です。BallFloat では、is_zero は [0,0][0, 0] だけでなく、ゼロを含むすべての有界区間(たとえば [−1,1][-1, 1])に対して真になります。別の意味を意図する場合は BallFloat::contains_zero を使うか、端点を比較してください。論理積は classify の段階で打ち切られるため、is_zero が空区間に対して sign を呼ぶことはありません。

///|
test "generic predicates" {
  inspect(@def.is_zero(@bin_float.BinFloat::from_double(-0.0)), content="true")
  inspect(
    @def.is_finite(@decimal.Decimal::from_string("NaN").unwrap()),
    content="false",
  )
  inspect(@def.is_nan(@ball_float.BallFloat::empty()), content="true")
  let around_zero = @ball_float.BallFloat::from_bounds(
    @bin_float.BinFloat::from_int(-1),
    @bin_float.BinFloat::from_int(1),
  )
  inspect(@def.is_zero(around_zero), content="true")
}

再エクスポートされる型

def はこれらの型を pub using で再エクスポートしているため、@def.RoundingMode と @lf_arith.RoundingMode は同じ型を指します。定義と意味論は Luna-Flow/arithmetic に記載されています。

pub using @arithmetic {type ArithmeticContext}
pub using @arithmetic {type ArithmeticError}
pub using @arithmetic {type ArithmeticErrorKind}
pub using @bigint {type BigInt}
pub using @arithmetic {type CertificationFailureDetail}
pub using @arithmetic {type CertificationFailureReason}
pub using @arithmetic {type CertificationStage}
pub using @arithmetic {type FpClass}
pub using @arithmetic {type RoundingMode}
別名floating における役割
ArithmeticContext算術のコンテキスト付きトレイトに渡される精度、丸め、および省略可能な指数の上下限。from_arithmetic_context コンストラクタによって BinaryContext / DecimalContext に対応付けられます。
ArithmeticError, ArithmeticErrorKindResult を返すすべての API(*_checked、try_*)および checked ラッパーパッケージの構造化エラー。
CertificationFailureDetail, CertificationFailureReason, CertificationStage種類が CertificationFailure である ArithmeticError のペイロード。正しく丸められた結果を予算内で認証できなかった初等関数を表します。
FpClassFloating::classify の結果。
RoundingModewith_precision と具体的なコンストラクタが受け付ける 5 つの丸め方向。
BigInt係数に用いられる、moonbitlang/core/bigint の任意精度整数。
///|
test "aliases name the arithmetic types" {
  let ctx : @def.ArithmeticContext = @lf_arith.ArithmeticContext::new(10)
  inspect(ctx.precision, content="10")
  let mode : @def.RoundingMode = ToNearestEven
  inspect(mode == @lf_arith.RoundingMode::ToNearestEven, content="true")
}

トレイト実装

Sign::equal, Sign::not_equal, PartialOrder::equal, PartialOrder::not_equal

これらのメソッドは導出された Eq インスタンスであり、a.equal(b) を引き続き使えるように明示的に昇格されています。== と != の使用を推奨します。

pub fn Sign::equal(Self, Self) -> Bool
pub fn Sign::not_equal(Self, Self) -> Bool
pub fn PartialOrder::equal(Self, Self) -> Bool
pub fn PartialOrder::not_equal(Self, Self) -> Bool

公開インターフェース全体

以下のスナップショットは、パッケージの生成された完全なインターフェースです。

// Generated using `moon info`, DON'T EDIT IT
package "Luna-Flow/floating/def"

import {
  "Luna-Flow/arithmetic",
  "moonbitlang/core/bigint",
}

// Values
pub fn[F : Floating] is_finite(F) -> Bool

pub fn[F : Floating] is_infinite(F) -> Bool

pub fn[F : Floating] is_nan(F) -> Bool

pub fn[F : Floating] is_zero(F) -> Bool

// Errors

// Types and methods
pub(all) enum PartialOrder {
  Less
  Equal
  Greater
  Unordered
} derive(Eq)
pub fn PartialOrder::equal(Self, Self) -> Bool
pub fn PartialOrder::not_equal(Self, Self) -> Bool

pub(all) enum Sign {
  Negative
  Zero
  Positive
} derive(Eq)
pub fn Sign::equal(Self, Self) -> Bool
pub fn Sign::not_equal(Self, Self) -> Bool

// Type aliases
pub using @arithmetic {type ArithmeticContext}

pub using @arithmetic {type ArithmeticError}

pub using @arithmetic {type ArithmeticErrorKind}

pub using @bigint {type BigInt}

pub using @arithmetic {type CertificationFailureDetail}

pub using @arithmetic {type CertificationFailureReason}

pub using @arithmetic {type CertificationStage}

pub using @arithmetic {type FpClass}

pub using @arithmetic {type RoundingMode}

// Traits
pub(open) trait Floating {
  fn classify(Self) -> @arithmetic.FpClass
  fn sign(Self) -> Sign
  fn precision(Self) -> Int
  fn with_precision(Self, Int, @arithmetic.RoundingMode) -> Self
  fn normalized(Self) -> Self
}