core API

このページは Luna-Flow/arithmetic パッケージ(src/ にあり、core として文書化される)のすべての公開項目を列挙する。例ではこれを @lf_arith としてインポートする。

import {
  "Luna-Flow/arithmetic" @lf_arith,
}

項目は四つの能力層と、それらが共有する値に分かれる。

層典型的なシグネチャ失敗の伝え方定義場所
非検査fn sqrt(Self) -> Selfなし:バックエンドが決める(NaN、中断、ラップアラウンド)elementary.mbt
検査付きfn sqrt_checked(Self, ArithmeticContext) -> Result[Self, ArithmeticError]Err(ArithmeticError)checked.mbt
コンテキスト付きfn sqrt_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]拒否は Err、注目すべき成功は診断contextual.mbt
包含区間の関係fn definitely_lt(Self, Self) -> Boolなし:順序ではなく関係checked.mbt

core 設計ページ は層を分ける理由と、その背後にある数学を導出する。core チュートリアル は具体的な作業の中でそれらを使う。

共有語彙

FpClass

FpClass は浮動小数点値を互いに素な三つのクラスのいずれかに分類する。

pub(all) enum FpClass {
  Finite
  Infinity
  NaN
} derive(Eq, @debug.Debug)

Finite はゼロ、非正規化数、正規化数を含み、Infinity は符号付きの両無限大を、NaN はすべての非数エンコーディングを含む。これらのクラスは分割をなすので、すべての値にちょうど一つが当てはまる。値のクラスは NumericFormatContextual::classify_contextual で得る。

test "classify a few doubles" {
  let inf = 1.0 / 0.0
  debug_inspect(
    @lf_arith.NumericFormatContextual::classify_contextual(inf),
    content="Infinity",
  )
  debug_inspect(
    @lf_arith.NumericFormatContextual::classify_contextual(0.0 / 0.0),
    content="NaN",
  )
  debug_inspect(
    @lf_arith.NumericFormatContextual::classify_contextual(-0.0),
    content="Finite",
  )
}

RoundingMode

RoundingMode はコンテキストが要求する丸めの方向を表す。

pub(all) enum RoundingMode {
  ToNearestEven
  TowardZero
  TowardPositive
  TowardNegative
  AwayFromZero
} derive(Eq)

隣り合う二つの表現可能な値 a<x<ba < x < b の間にある実数 xx について:

モード戻り値
ToNearestEvena,ba, b のうち近い方。等距離なら最後の桁が偶数の方
TowardZero絶対値の小さい方(切り捨て)
TowardPositivebb(天井)
TowardNegativeaa(床)
AwayFromZero絶対値の大きい方

AwayFromZero はゼロから遠ざかる方向への有向丸め(General Decimal Arithmetic 仕様の ROUND_UP)であり、等距離時にゼロから遠ざける最近接丸めではない。モードは要求にすぎない。パッケージはそれを ArithmeticContext に保存し、従うかどうかは各バックエンドが決める。組み込みの Float と Double のインスタンスはこれを無視する。

算術コンテキスト

ArithmeticContext

ArithmeticContext は、検査付き演算やコンテキスト付き演算が実行されるときの作業精度、丸めモード、指数範囲を記録する不変のレコードである。

pub struct ArithmeticContext {
  precision : Int
  rounding : RoundingMode
  e_min : Int?
  e_max : Int?
  clamp : Bool
} derive(Eq)
  • precision はバックエンドの基数での仮数の桁数である(十進バックエンドなら十進桁、二進バックエンドならビット)。常に 1 以上である。
  • rounding は要求される RoundingMode である。
  • e_min と e_max は正規化された結果の調整済み指数を制限する。None は無制限を意味する。両方があるときは e_min <= e_max である。
  • clamp は、十進交換形式と同様に、指数を形式の範囲へクランプするようバックエンドに求める。

フィールドはパッケージ外からは読み取り専用である。値は ArithmeticContext::new かプリセットで作る。コンテキストは通常の引数として渡され、グローバルなコンテキストもスレッドローカルなコンテキストも存在しない。

ArithmeticContext::new

ArithmeticContext::new は精度と省略可能な設定からコンテキストを作る。

pub fn ArithmeticContext::new(Int, rounding? : RoundingMode, e_min? : Int, e_max? : Int, clamp? : Bool) -> ArithmeticContext

rounding の既定値は ToNearestEven、clamp は false、二つの指数制限はどちらも None である。1 未満の精度は 1 に引き上げられる。両方の制限が与えられ e_min > e_max のとき、この関数は ArithmeticContext::new: e_min must not exceed e_max で中断する。

test "build a context" {
  let ctx = @lf_arith.ArithmeticContext::new(
    24,
    rounding=@lf_arith.RoundingMode::TowardNegative,
    e_min=-126,
    e_max=127,
  )
  inspect(ctx.precision, content="24")
  inspect(ctx.clamp, content="false")
  inspect(@lf_arith.ArithmeticContext::new(0).precision, content="1")
}

ArithmeticContext::decimal32, ArithmeticContext::decimal64, ArithmeticContext::decimal128

これらのプリセットは IEEE 754 十進交換形式のコンテキストを返す。

pub fn ArithmeticContext::decimal32() -> ArithmeticContext
pub fn ArithmeticContext::decimal64() -> ArithmeticContext
pub fn ArithmeticContext::decimal128() -> ArithmeticContext
プリセットprecisione_mine_maxroundingclamp
decimal327-9596ToNearestEventrue
decimal6416-383384ToNearestEventrue
decimal12834-61436144ToNearestEventrue

IEEE 754 が要求するとおり、どのプリセットでも emin⁡=1−emax⁡e_{\min} = 1 - e_{\max} である。

test "decimal presets" {
  let d64 = @lf_arith.ArithmeticContext::decimal64()
  inspect(d64.precision, content="16")
  inspect(d64.e_min == Some(-383), content="true")
  inspect(d64 == @lf_arith.ArithmeticContext::new(16, e_min=-383, e_max=384, clamp=true), content="true")
}

エラー

ArithmeticErrorKind

ArithmeticErrorKind は ArithmeticError の種別である。

pub enum ArithmeticErrorKind {
  DivisionByZero
  ParseError
  DomainError
  FormatError
  UnsupportedOperation
  UnorderedComparison
  CertificationFailure(CertificationFailureDetail)
} derive(Eq)
種別意味
DivisionByZeroゼロ以外の値または NaN をゼロで割った、あるいはゼロの逆数を取った
ParseError値を表さないテキスト
DomainError演算の数学的な定義域外の引数。0/00/0 のような不定形を含む
FormatError対象の形式が保持または表示できない値
UnsupportedOperationバックエンドが実装していない演算またはコンテキスト
UnorderedComparisonNaN のような順序付けられない値を含む比較
CertificationFailure証明付きバックエンドが結果を証明できなかった。詳細を保持する

この列挙型は pub(all) ではなく pub である。マッチはできるが、エラーは下記の ArithmeticError コンストラクタで作る。

ArithmeticError

ArithmeticError は、すべての検査付きトレイトとコンテキスト付きトレイトが返す構造化エラーである。

pub struct ArithmeticError {
  kind : ArithmeticErrorKind
  message : String
} derive(Eq)

kind は機械可読な種別、message は人間向けの説明である。二つのエラーは両方のフィールドが等しいときに等しい。

エラーのコンストラクタ

ArithmeticError::division_by_zero、ArithmeticError::parse_error、ArithmeticError::domain_error、ArithmeticError::format_error、ArithmeticError::unsupported、ArithmeticError::unordered_comparison は、与えられたメッセージを持つ対応する種別のエラーを作る。

pub fn ArithmeticError::division_by_zero(String) -> ArithmeticError
pub fn ArithmeticError::parse_error(String) -> ArithmeticError
pub fn ArithmeticError::domain_error(String) -> ArithmeticError
pub fn ArithmeticError::format_error(String) -> ArithmeticError
pub fn ArithmeticError::unsupported(String) -> ArithmeticError
pub fn ArithmeticError::unordered_comparison(String) -> ArithmeticError

unsupported が作る種別は UnsupportedOperation である。

エラーの述語

ArithmeticError::is_division_by_zero、is_parse_error、is_domain_error、is_format_error、is_unsupported、is_unordered_comparison、is_certification_failure はエラーの種別を判定する。

pub fn ArithmeticError::is_division_by_zero(ArithmeticError) -> Bool
pub fn ArithmeticError::is_parse_error(ArithmeticError) -> Bool
pub fn ArithmeticError::is_domain_error(ArithmeticError) -> Bool
pub fn ArithmeticError::is_format_error(ArithmeticError) -> Bool
pub fn ArithmeticError::is_unsupported(ArithmeticError) -> Bool
pub fn ArithmeticError::is_unordered_comparison(ArithmeticError) -> Bool
pub fn ArithmeticError::is_certification_failure(ArithmeticError) -> Bool

どのエラーについても、ちょうど一つの述語が真になる。

test "build and classify errors" {
  let err = @lf_arith.ArithmeticError::domain_error("log of a negative number")
  inspect(err.is_domain_error(), content="true")
  inspect(err.is_division_by_zero(), content="false")
  inspect(err.message, content="log of a negative number")
  inspect(
    @lf_arith.ArithmeticError::unsupported("no decimal backend").is_unsupported(),
    content="true",
  )
}

ArithmeticError::certification_failure

ArithmeticError::certification_failure は CertificationFailureDetail を種別 CertificationFailure のエラーに包む。

pub fn ArithmeticError::certification_failure(CertificationFailureDetail) -> ArithmeticError

メッセージは certified evaluation failed for <operation> であり、<operation> は詳細から取られる。

ArithmeticError::certification_failure_detail

ArithmeticError::certification_failure_detail は証明失敗の詳細を返し、それ以外の種別では None を返す。

pub fn ArithmeticError::certification_failure_detail(ArithmeticError) -> CertificationFailureDetail?

証明の失敗

証明付きバックエンドは段階からなるパイプラインで関数を評価し、結果が目標を満たすことを証明する。それができないときは、パイプラインがどこで止まり、なぜ止まったかを報告する。設計ページ がこのパイプラインを説明する。

CertificationStage

CertificationStage は証明付き評価が失敗した段階を表す。

pub(all) enum CertificationStage {
  RangeReduction
  SeriesEvaluation
  EnclosurePropagation
  TargetRounding
} derive(Eq)
段階その段階で行う処理
RangeReduction引数を小さな主区間へ還元する
SeriesEvaluation打ち切り級数や近似を剰余の上界とともに評価する
EnclosurePropagation残りの演算を通して誤差の包含区間を伝播する
TargetRounding最終的な包含区間を目標精度へ丸める

CertificationFailureReason

CertificationFailureReason は段階を証明できなかった理由を示す。

pub(all) enum CertificationFailureReason {
  RangeNotCertified
  SeriesDidNotConverge
  InvalidEnclosure
  ResourceLimit
  RefinementBudgetExhausted
} derive(Eq)
理由意味
RangeNotCertified還元後の引数が主範囲にあることを示せなかった
SeriesDidNotConverge剰余の上界が要求された許容誤差を下回らなかった
InvalidEnclosure途中の包含区間が空、非有界、またはその他の理由で使えなかった
ResourceLimit精度またはサイズの上限に達した
RefinementBudgetExhausted精度を繰り返し上げても丸めが決まらなかった

CertificationFailureDetail

CertificationFailureDetail は証明失敗の証拠を記録する。

pub struct CertificationFailureDetail {
  operation : String
  stage : CertificationStage
  reason : CertificationFailureReason
  target_precision : Int
  work_precision : Int
  refinements : Int
} derive(Eq)

operation は演算の名前(例えば "exp")、target_precision は結果が要求された精度、work_precision は最後に試した内部精度、refinements は精度を上げた回数である。

CertificationFailureDetail::new

CertificationFailureDetail::new は詳細を作り、カウンタを正規化する。

pub fn CertificationFailureDetail::new(String, CertificationStage, CertificationFailureReason, Int, Int, Int) -> CertificationFailureDetail

引数は順に、演算、段階、理由、目標精度、作業精度、精度向上の回数である。二つの精度は 1 以上に、回数は 0 以上に引き上げられる。

詳細のアクセサ

CertificationFailureDetail::operation、stage、reason、target_precision、work_precision、refinements は同名のフィールドを返す。

pub fn CertificationFailureDetail::operation(CertificationFailureDetail) -> String
pub fn CertificationFailureDetail::stage(CertificationFailureDetail) -> CertificationStage
pub fn CertificationFailureDetail::reason(CertificationFailureDetail) -> CertificationFailureReason
pub fn CertificationFailureDetail::target_precision(CertificationFailureDetail) -> Int
pub fn CertificationFailureDetail::work_precision(CertificationFailureDetail) -> Int
pub fn CertificationFailureDetail::refinements(CertificationFailureDetail) -> Int
test "certification failure round trip" {
  let detail = @lf_arith.CertificationFailureDetail::new(
    "exp",
    @lf_arith.CertificationStage::TargetRounding,
    @lf_arith.CertificationFailureReason::RefinementBudgetExhausted,
    53,
    384,
    -2,
  )
  let err = @lf_arith.ArithmeticError::certification_failure(detail)
  inspect(err.message, content="certified evaluation failed for exp")
  inspect(err.is_certification_failure(), content="true")
  guard err.certification_failure_detail() is Some(d) else { fail("no detail") }
  inspect(d.work_precision(), content="384")
  inspect(d.refinements(), content="0")
  inspect(d.stage() == @lf_arith.CertificationStage::TargetRounding, content="true")
}

診断と結果

ArithmeticDiagnostics

ArithmeticDiagnostics は、成功したコンテキスト付き演算が立てうる六つの条件フラグの集合である。

pub struct ArithmeticDiagnostics {
  inexact : Bool
  rounded : Bool
  overflow : Bool
  underflow : Bool
  subnormal : Bool
  clamped : Bool
} derive(Eq, @debug.Debug)
フラグ立つ条件
inexact返された値が正確な結果と異なる
rounded結果がコンテキストの精度へ丸められた(損失がない場合もある)
overflow正確な結果が最大の有限値の大きさを超えた
underflow結果が極めて小さく(正規範囲未満)、かつ不正確である
subnormal結果が正規範囲未満である
clamped形式に収めるため指数が変更された

意味は IEEE 754 と General Decimal Arithmetic の条件に従う。バックエンドは検出できるフラグだけを立てる。

ArithmeticDiagnostics::empty

ArithmeticDiagnostics::empty はすべてのフラグが false の値を返す。

pub fn ArithmeticDiagnostics::empty() -> ArithmeticDiagnostics

ArithmeticDiagnostics::new

ArithmeticDiagnostics::new はラベル付きのフラグから診断を作る。各フラグの既定値は false である。

pub fn ArithmeticDiagnostics::new(inexact? : Bool, rounded? : Bool, overflow? : Bool, underflow? : Bool, subnormal? : Bool, clamped? : Bool) -> ArithmeticDiagnostics

ArithmeticDiagnostics::combine

ArithmeticDiagnostics::combine は二つの診断を、各フラグの論理和で併合する。

pub fn ArithmeticDiagnostics::combine(ArithmeticDiagnostics, ArithmeticDiagnostics) -> ArithmeticDiagnostics

combine は結合的、可換、冪等であり、empty() がその単位元である。したがって計算の診断をどの順序で畳み込んでも同じ結果になる。

test "combine diagnostics" {
  let a = @lf_arith.ArithmeticDiagnostics::new(inexact=true, rounded=true)
  let b = @lf_arith.ArithmeticDiagnostics::new(underflow=true)
  let both = a.combine(b)
  inspect(both.inexact && both.underflow, content="true")
  inspect(both.overflow, content="false")
  inspect(a.combine(@lf_arith.ArithmeticDiagnostics::empty()) == a, content="true")
  inspect(a.combine(b) == b.combine(a), content="true")
}

ArithmeticOutcome

ArithmeticOutcome[T] は、成功したコンテキスト付き演算の値とその診断を組にする。

pub struct ArithmeticOutcome[T] {
  value : T
  diagnostics : ArithmeticDiagnostics
} derive(Eq, @debug.Debug)

ArithmeticOutcome::exact

ArithmeticOutcome::exact は値を空の診断で包む。

pub fn[T] ArithmeticOutcome::exact(T) -> ArithmeticOutcome[T]

ArithmeticOutcome::with_diagnostics

ArithmeticOutcome::with_diagnostics は値を与えられた診断で包む。

pub fn[T] ArithmeticOutcome::with_diagnostics(T, ArithmeticDiagnostics) -> ArithmeticOutcome[T]
test "build outcomes" {
  let exact = @lf_arith.ArithmeticOutcome::exact(2.5)
  inspect(exact.value, content="2.5")
  inspect(exact.diagnostics == @lf_arith.ArithmeticDiagnostics::empty(), content="true")
  let rounded = @lf_arith.ArithmeticOutcome::with_diagnostics(
    0.1,
    @lf_arith.ArithmeticDiagnostics::new(inexact=true, rounded=true),
  )
  inspect(rounded.diagnostics.inexact, content="true")
}

等価性

FpClass::equal、RoundingMode::equal、ArithmeticContext::equal、ArithmeticDiagnostics::equal、ArithmeticOutcome::equal、ArithmeticError::equal、ArithmeticErrorKind::equal、CertificationStage::equal、CertificationFailureReason::equal、CertificationFailureDetail::equal は導出された Eq インスタンスをメソッドとして昇格したものである。

pub fn FpClass::equal(FpClass, FpClass) -> Bool
pub fn RoundingMode::equal(RoundingMode, RoundingMode) -> Bool
pub fn ArithmeticContext::equal(ArithmeticContext, ArithmeticContext) -> Bool
pub fn ArithmeticDiagnostics::equal(ArithmeticDiagnostics, ArithmeticDiagnostics) -> Bool
pub fn[T : Eq] ArithmeticOutcome::equal(ArithmeticOutcome[T], ArithmeticOutcome[T]) -> Bool
pub fn ArithmeticError::equal(ArithmeticError, ArithmeticError) -> Bool
pub fn ArithmeticErrorKind::equal(ArithmeticErrorKind, ArithmeticErrorKind) -> Bool
pub fn CertificationStage::equal(CertificationStage, CertificationStage) -> Bool
pub fn CertificationFailureReason::equal(CertificationFailureReason, CertificationFailureReason) -> Bool
pub fn CertificationFailureDetail::equal(CertificationFailureDetail, CertificationFailureDetail) -> Bool

等価性は構造的で、フィールドごとに比較する。演算子 == と != を優先すること。メソッド形式は equal を関数として渡すコードのためにある。ArithmeticOutcome::equal は値を T の Eq で比較するので、Double では NaN を含む二つの結果は等しくならない。

非検査能力トレイト

各非検査トレイトは一つの能力であり、メソッドは Self を返す。トレイトは定義域を定めない。定義域の外で何が起きるか(NaN、無限大、中断、ラップアラウンド)はインスタンスが決める。すべて pub(open) なので、独自の型に実装できる。

Sqrt

Sqrt は平方根を提供する。

pub(open) trait Sqrt {
  fn sqrt(Self) -> Self
}

Float と Double では sqrt は IEEE 754 に従い、負の入力には NaN を返し、−0=−0\sqrt{-0} = -0 である。

fn[T : Add + Mul + @lf_arith.Sqrt] hypot_naive(x : T, y : T) -> T {
  @lf_arith.Sqrt::sqrt(x * x + y * y)
}

test "generic hypotenuse" {
  inspect(hypot_naive(3.0, 4.0), content="5")
  inspect(hypot_naive((5.0 : Float), (12.0 : Float)), content="13")
}

Cbrt

Cbrt は実数の立方根を提供する。

pub(open) trait Cbrt {
  fn cbrt(Self) -> Self
}

Float と Double では立方根は実数直線全体で実数値をとるので、−83=−2\sqrt[3]{-8} = -2 である。

Radical

Radical は連言 Sqrt + Cbrt であり、独自のメソッドは持たない。

pub(open) trait Radical : Sqrt + Cbrt {
}
fn[T : @lf_arith.Radical] roots(x : T) -> (T, T) {
  (@lf_arith.Sqrt::sqrt(x), @lf_arith.Cbrt::cbrt(x))
}

test "radical" {
  let (s, c) = roots(64.0)
  inspect(s, content="8")
  inspect(c, content="4")
  inspect(@lf_arith.Cbrt::cbrt(-8.0), content="-2")
}

Exponential

Exponential は exe^x(exp)と 2x2^x(exp2)を提供する。

pub(open) trait Exponential {
  fn exp(Self) -> Self
  fn exp2(Self) -> Self
}

Logarithmic

Logarithmic は自然対数(ln)、二進対数(log2)、常用対数(log10)を提供する。

pub(open) trait Logarithmic {
  fn ln(Self) -> Self
  fn log2(Self) -> Self
  fn log10(Self) -> Self
}

Float と Double では、負の引数は NaN を、ゼロは −∞-\infty を与える。

test "exponentials and logarithms" {
  inspect(@lf_arith.Exponential::exp(0.0), content="1")
  inspect(@lf_arith.Exponential::exp2(10.0), content="1024")
  inspect(@lf_arith.Logarithmic::log2(1024.0), content="10")
  inspect(@lf_arith.Logarithmic::log10(1000.0), content="3")
  inspect(@lf_arith.Logarithmic::ln(1.0), content="0")
}

Power

Power は底を同じ型の指数でべき乗する。

pub(open) trait Power {
  fn pow(Self, Self) -> Self
}
インスタンス意味
Float, DoubleC の pow 関数:実数のべき乗。負の底と非整数の指数では NaN
Int, Int16, Int64Z/2k\mathbb{Z}/2^k での正確なべき乗(オーバーフロー時はラップアラウンド)。負の指数では中断する
UInt, UInt16, UInt64Z/2k\mathbb{Z}/2^k での正確なべき乗(オーバーフロー時はラップアラウンド)
BigInt正確なべき乗。負の指数では中断する

整数インスタンスは二進べき乗法を使い、乗算は O(log⁡n)O(\log n) 回である。どの底でも x0=1x^0 = 1 であり、000^0 も含む。

test "power" {
  inspect(@lf_arith.Power::pow(2, 10), content="1024")
  inspect(@lf_arith.Power::pow(0, 0), content="1")
  inspect(@lf_arith.Power::pow(2U, 32U), content="0")
  inspect(@lf_arith.Power::pow(2.0, 0.5), content="1.4142135623730951")
  inspect(@lf_arith.Power::pow(10N, 20N), content="100000000000000000000")
}

Trigonometric

Trigonometric は角度(提供されるインスタンスではラジアン)の sin、cos、tan を提供する。

pub(open) trait Trigonometric {
  fn sin(Self) -> Self
  fn cos(Self) -> Self
  fn tan(Self) -> Self
}

InverseTrigonometric

InverseTrigonometric は主枝 asin、acos、atan と二引数の atan2(y, x) を提供する。

pub(open) trait InverseTrigonometric {
  fn asin(Self) -> Self
  fn acos(Self) -> Self
  fn atan(Self) -> Self
  fn atan2(Self, Self) -> Self
}

Float と Double での値域は、asin と atan が [−π/2,π/2][-\pi/2, \pi/2]、acos が [0,π][0, \pi]、atan2 が [−π,π][-\pi, \pi] である。後者は点 (x,y)(x, y) の偏角を返し、負の xx 軸上ではゼロである yy の符号によって π\pi と −π-\pi のどちらかが選ばれる。

test "trigonometry" {
  inspect(@lf_arith.Trigonometric::sin(0.0), content="0")
  inspect(@lf_arith.Trigonometric::cos(0.0), content="1")
  let pi : Double = @lf_arith.Constants::pi()
  inspect(@lf_arith.InverseTrigonometric::atan2(0.0, -1.0) == pi, content="true")
  inspect(@lf_arith.InverseTrigonometric::acos(1.0), content="0")
}

Hyperbolic

Hyperbolic は sinh、cosh、tanh を提供する。

pub(open) trait Hyperbolic {
  fn sinh(Self) -> Self
  fn cosh(Self) -> Self
  fn tanh(Self) -> Self
}

InverseHyperbolic

InverseHyperbolic は asinh、acosh、atanh を提供する。

pub(open) trait InverseHyperbolic {
  fn asinh(Self) -> Self
  fn acosh(Self) -> Self
  fn atanh(Self) -> Self
}

Float と Double では、acosh は [1,∞)[1, \infty) で、atanh は [−1,1][-1, 1] で定義される。その外では結果は NaN であり、atanh⁡(±1)=±∞\operatorname{atanh}(\pm 1) = \pm\infty である。

test "hyperbolic" {
  inspect(@lf_arith.Hyperbolic::cosh(0.0), content="1")
  inspect(@lf_arith.Hyperbolic::tanh(0.0), content="0")
  inspect(@lf_arith.InverseHyperbolic::acosh(1.0), content="0")
  inspect(@lf_arith.InverseHyperbolic::atanh(2.0).is_nan(), content="true")
}

Constants

Constants は Self における π\pi、τ=2π\tau = 2\pi、ee を提供する。

pub(open) trait Constants {
  fn pi() -> Self
  fn tau() -> Self
  fn e() -> Self
}

これらのメソッドは引数をとらないので、目的の型が分かる形で呼び出すこと。Double では pi は @math.PI、tau は 2.0 * @math.PI(二倍は指数を変えるだけなので正確)、e は exp(1.0) である。Float では Double の値を Float へ丸めたものである。

test "constants" {
  let pi : Double = @lf_arith.Constants::pi()
  let tau : Double = @lf_arith.Constants::tau()
  let e : Float = @lf_arith.Constants::e()
  inspect(tau == 2.0 * pi, content="true")
  inspect(pi, content="3.141592653589793")
  inspect(e > (2.71 : Float), content="true")
}

検査付き能力トレイト

検査付きトレイトは Result[_, ArithmeticError] を返すので、拒否された演算は呼び出し側が処理しなければならない値になる。精度に依存しうるメソッドは ArithmeticContext を受け取る。組み込みの Float と Double のインスタンスはそれを受け取るが、読みはしない。

SqrtChecked

SqrtChecked は平方根を計算するか、定義域エラーを報告する。

pub(open) trait SqrtChecked {
  fn sqrt_checked(Self, ArithmeticContext) -> Result[Self, ArithmeticError]
}

定義域は各インスタンスが定め、トレイトは順序を仮定しない。Float と Double では、引数 x<0x < 0(−∞-\infty を含む)は DomainError になり、−0-0、+∞+\infty、NaN はそのまま Sqrt::sqrt に渡される。

test "checked square root" {
  let ctx = @lf_arith.ArithmeticContext::decimal64()
  inspect(@lf_arith.SqrtChecked::sqrt_checked(2.25, ctx).unwrap(), content="1.5")
  guard @lf_arith.SqrtChecked::sqrt_checked(-1.0, ctx) is Err(err) else {
    fail("expected a domain error")
  }
  inspect(err.is_domain_error(), content="true")
  inspect(err.message, content="square root is undefined for negative real inputs")
}

DivChecked

DivChecked は除算を行うか、商が定義されない理由を報告する。

pub(open) trait DivChecked {
  fn div_checked(Self, Self, ArithmeticContext) -> Result[Self, ArithmeticError]
}

Float と Double では、検査は次の順に行われる:

場合戻り値
±0/±0\pm 0 / \pm 0DomainError (zero divided by zero is undefined)
±∞/±∞\pm\infty / \pm\inftyDomainError (infinity divided by infinity is undefined)
それ以外の x/±0x / \pm 0(NaN / 0/\, 0 を含む)DivisionByZero (division by zero)
それ以外Ok(x / y)。NaN は IEEE どおり伝播する
test "checked division" {
  let ctx = @lf_arith.ArithmeticContext::decimal64()
  inspect(@lf_arith.DivChecked::div_checked(10.0, 4.0, ctx).unwrap(), content="2.5")
  let zero_by_zero = @lf_arith.DivChecked::div_checked(0.0, 0.0, ctx)
  inspect(zero_by_zero is Err(e) && e.is_domain_error(), content="true")
  let one_by_zero = @lf_arith.DivChecked::div_checked(1.0, -0.0, ctx)
  inspect(one_by_zero is Err(e) && e.is_division_by_zero(), content="true")
}

CompareChecked

CompareChecked は二つの値を比較するか、順序付けられないことを報告する。

pub(open) trait CompareChecked {
  fn compare_checked(Self, Self) -> Result[Int, ArithmeticError]
}

順序付けられる入力では結果は -1、0、1 のいずれかである。Float と Double では、NaN のオペランドは UnorderedComparison になり、−0-0 と +0+0 は等しいと比較される。

test "checked comparison" {
  inspect(@lf_arith.CompareChecked::compare_checked(1.0, 2.0).unwrap(), content="-1")
  inspect(@lf_arith.CompareChecked::compare_checked(-0.0, 0.0).unwrap(), content="0")
  let nan = 0.0 / 0.0
  let r = @lf_arith.CompareChecked::compare_checked(nan, 1.0)
  inspect(r is Err(e) && e.is_unordered_comparison(), content="true")
}

PowNatChecked

PowNatChecked は値を非負整数乗する。

pub(open) trait PowNatChecked {
  fn pow_nat_checked(Self, UInt, ArithmeticContext) -> Result[Self, ArithmeticError]
}

x0x^0 は乗法単位元であり、000^0 も含む。Float と Double のインスタンスは失敗しない。丸めを伴う乗算が高々 2⌊log⁡2n⌋2\lfloor\log_2 n\rfloor 回の二進べき乗法を使い、通常の IEEE の積と同様に ±∞\pm\infty へオーバーフローし、ゼロへアンダーフローする。丸め誤差の上界は設計ページで示す。

PowIntChecked

PowIntChecked は値を符号付き整数乗する。

pub(open) trait PowIntChecked {
  fn pow_int_checked(Self, Int, ArithmeticContext) -> Result[Self, ArithmeticError]
}

負の指数は逆数を意味する。ゼロの底と負の指数は、エラーとして(包含区間型なら文書化された包含区間として)報告しなければならず、黙って不正な値を返してはならない。Float と Double では:

  • x0=1x^0 = 1;
  • n>0n > 0 の xnx^n は pow_nat_checked(x, n) である。
  • x−nx^{-n} は x=±0x = \pm 0 のとき DivisionByZero であり、それ以外は div_checked(1, x^n) である。最も小さい負の Int の指数も、符号反転のオーバーフローなしに扱われる。
test "checked integer powers" {
  let ctx = @lf_arith.ArithmeticContext::decimal64()
  inspect(@lf_arith.PowNatChecked::pow_nat_checked(3.0, 4U, ctx).unwrap(), content="81")
  inspect(@lf_arith.PowNatChecked::pow_nat_checked(0.0, 0U, ctx).unwrap(), content="1")
  inspect(@lf_arith.PowIntChecked::pow_int_checked(2.0, -3, ctx).unwrap(), content="0.125")
  let r = @lf_arith.PowIntChecked::pow_int_checked(0.0, -1, ctx)
  inspect(r is Err(e) && e.is_division_by_zero(), content="true")
}

ParseChecked

ParseChecked はコンテキストのもとでテキストを Self へ解析する。

pub(open) trait ParseChecked {
  fn parse_checked(String, ArithmeticContext) -> Result[Self, ArithmeticError]
}

コンテキストにより、十進バックエンドは解析した値を自身の精度へ丸められる。失敗には ParseError を使い、テキストは正しい形式だが形式がそれを保持できない場合は FormatError を使う。このパッケージはインスタンスを提供しない。解析は、十進型のようにテキスト形式を持つバックエンドの役割である。

コンテキスト付き能力トレイト

コンテキスト付きトレイトは Result[ArithmeticOutcome[Self], ArithmeticError] を返す。Err は演算が拒否されたことを意味し、Ok は値と、それを生成する間にバックエンドが検出した診断を運ぶ。

AddContextual, SubContextual, MulContextual, DivContextual

これら四つのトレイトは +、-、*、/ のコンテキスト付き版である。

pub(open) trait AddContextual {
  fn add_contextual(Self, Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}
pub(open) trait SubContextual {
  fn sub_contextual(Self, Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}
pub(open) trait MulContextual {
  fn mul_contextual(Self, Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}
pub(open) trait DivContextual {
  fn div_contextual(Self, Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

コンテキストに忠実なバックエンドは、正確な結果をコンテキストのもとで丸めた fl⁡(x∘y)\operatorname{fl}(x \circ y) を返し、その丸めが引き起こした診断を設定する。Float と Double では加算、減算、乗算は失敗せず、div_contextual は DivChecked::div_checked が失敗するときにちょうど失敗する。

test "contextual arithmetic" {
  let ctx = @lf_arith.ArithmeticContext::decimal64()
  let q = @lf_arith.DivContextual::div_contextual(10.0, 4.0, ctx).unwrap()
  inspect(q.value, content="2.5")
  let s = @lf_arith.AddContextual::add_contextual(0.1, 0.2, ctx).unwrap()
  inspect(s.value, content="0.30000000000000004")
  inspect(s.diagnostics.inexact, content="false")
  inspect(@lf_arith.DivContextual::div_contextual(1.0, 0.0, ctx) is Err(_), content="true")
}

AbsContextual

AbsContextual はコンテキスト付きの絶対値である。

pub(open) trait AbsContextual {
  fn abs_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

コンテキストの精度がオペランドより低いとき、バックエンドは丸めてよい。Float と Double では正確な IEEE の abs である。

SqrtContextual

SqrtContextual はコンテキスト付きの平方根である。

pub(open) trait SqrtContextual {
  fn sqrt_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

Float と Double では、SqrtChecked::sqrt_checked が失敗するときにちょうど失敗する。

ExpContextual

ExpContextual はコンテキスト付きの自然指数関数である。

pub(open) trait ExpContextual {
  fn exp_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

Float と Double では、Exponential::exp を正確な結果として包んだものである。

test "contextual sqrt, abs and exp" {
  let ctx = @lf_arith.ArithmeticContext::decimal64()
  inspect(@lf_arith.SqrtContextual::sqrt_contextual(9.0, ctx).unwrap().value, content="3")
  inspect(@lf_arith.SqrtContextual::sqrt_contextual(-9.0, ctx) is Err(_), content="true")
  inspect(@lf_arith.AbsContextual::abs_contextual(-2.0, ctx).unwrap().value, content="2")
  inspect(@lf_arith.ExpContextual::exp_contextual(0.0, ctx).unwrap().value, content="1")
}

IntegralContextual

IntegralContextual はコンテキストのもとで MoonBit の Int を Self へ埋め込む。

pub(open) trait IntegralContextual {
  fn from_int_contextual(Int, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

入力は常に Int であり、より広い整数や任意精度の入力には別の能力が必要である。バックエンドはサポートしないコンテキストを拒否してよい。Double ではすべての Int が正確に表される。Float では、∣n∣>224|n| > 2^{24} でその大きさにおける間隔の倍数でない整数は丸められ、そのとき結果には inexact と rounded が設定される。

test "embed integers" {
  let ctx = @lf_arith.ArithmeticContext::new(24)
  let exact : @lf_arith.ArithmeticOutcome[Float] = @lf_arith.IntegralContextual::from_int_contextual(
    16_777_216, ctx,
  ).unwrap()
  inspect(exact.diagnostics.inexact, content="false")
  let rounded : @lf_arith.ArithmeticOutcome[Float] = @lf_arith.IntegralContextual::from_int_contextual(
    16_777_217, ctx,
  ).unwrap()
  inspect(rounded.value, content="16777216")
  inspect(rounded.diagnostics.inexact && rounded.diagnostics.rounded, content="true")
}

AdjacentContextual

AdjacentContextual は隣接する表現可能な値へ進む。

pub(open) trait AdjacentContextual {
  fn next_plus_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn next_minus_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn next_toward_contextual(Self, Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

next_plus_contextual(x) は xx より大きい最小の表現可能な値、next_minus_contextual(x) は xx より小さい最大の表現可能な値である。next_toward_contextual(x, t) は xx から tt の方向へ一歩進む。x=tx = t のときは tt を返すので、next_toward(0.0, -0.0) は −0-0 である。表現可能な集合がコンテキストに依存するバックエンドは、範囲に関する条件を診断で報告する。

Float と Double では、表現可能な集合は固定された IEEE 二進形式であり、結果は常に正確である:

入力next_plusnext_minus
±0\pm 0最小の正の非正規化数最小の負の非正規化数(絶対値が最小)
最大の有限値+∞+\infty直前の値
+∞+\infty+∞+\infty最大の有限値
−∞-\infty−(largest finite)-(\text{largest finite})−∞-\infty
NaNNaNNaN

どちらかのオペランドが NaN なら、next_toward は NaN を返す。

test "adjacent doubles" {
  let ctx = @lf_arith.ArithmeticContext::new(53)
  let up = @lf_arith.AdjacentContextual::next_plus_contextual(1.0, ctx).unwrap()
  inspect(up.value - 1.0, content="2.220446049250313e-16")
  let tiny = @lf_arith.AdjacentContextual::next_plus_contextual(0.0, ctx).unwrap()
  inspect(tiny.value.reinterpret_as_uint64(), content="1")
  let down = @lf_arith.AdjacentContextual::next_toward_contextual(1.0, 0.0, ctx).unwrap()
  inspect(1.0 - down.value, content="1.1102230246251565e-16")
}

ConstantsContextual

ConstantsContextual はコンテキストのもとで π\pi、τ\tau、ee を生成する。

pub(open) trait ConstantsContextual {
  fn pi_contextual(ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn tau_contextual(ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn e_contextual(ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

定数がコンテキストに従い、診断が意味を持つ場合にだけ実装すること。証明付きバックエンドは、目標の丸めを証明できないとき CertificationFailure エラーを返す。Float と Double はこれを実装しない。設計ページを参照。

HyperbolicContextual

HyperbolicContextual はコンテキストのもとで sinh、cosh、tanh を提供する。

pub(open) trait HyperbolicContextual {
  fn sinh_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn cosh_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
  fn tanh_contextual(Self, ArithmeticContext) -> Result[ArithmeticOutcome[Self], ArithmeticError]
}

ConstantsContextual と同じ規則が適用され、Float と Double はこれも実装しない。

NumericFormatContextual

NumericFormatContextual は、コンテキストのもとでの Self の数値形式、すなわち特別な値と値のクラスを記述する。

pub(open) trait NumericFormatContextual {
  fn zero_contextual(ArithmeticContext) -> Self
  fn one_contextual(ArithmeticContext) -> Self
  fn epsilon_contextual(ArithmeticContext) -> Self
  fn min_normal_contextual(ArithmeticContext) -> Self
  fn max_finite_contextual(ArithmeticContext) -> Self
  fn classify_contextual(Self) -> FpClass
}

epsilon_contextual はマシンイプシロン ε=β1−p\varepsilon = \beta^{1-p}、つまり 11 からその次に大きい表現可能な値までの距離である。最近接丸めの単位丸め誤差は u=ε/2u = \varepsilon / 2 である。これらのメソッドは失敗しない。固定形式ではコンテキストは無視される:

メソッドFloat(binary32)Double(binary64)
epsilon_contextual2−23≈1.19×10−72^{-23} \approx 1.19 \times 10^{-7}2−52≈2.22×10−162^{-52} \approx 2.22 \times 10^{-16}
min_normal_contextual2−126≈1.18×10−382^{-126} \approx 1.18 \times 10^{-38}2−1022≈2.23×10−3082^{-1022} \approx 2.23 \times 10^{-308}
max_finite_contextual(2−2−23) 2127≈3.40×1038(2 - 2^{-23})\,2^{127} \approx 3.40 \times 10^{38}(2−2−52) 21023≈1.80×10308(2 - 2^{-52})\,2^{1023} \approx 1.80 \times 10^{308}
test "format constants" {
  let ctx = @lf_arith.ArithmeticContext::new(53)
  let eps : Double = @lf_arith.NumericFormatContextual::epsilon_contextual(ctx)
  inspect(eps, content="2.220446049250313e-16")
  let one : Double = @lf_arith.NumericFormatContextual::one_contextual(ctx)
  inspect(one + eps > one, content="true")
  inspect(one + eps / 2.0 == one, content="true")
}

包含区間の関係

包含区間(enclosure)とは、ある集合(区間やボールなど)に属すると分かっている未知の実数を表す値である。これら五つのトレイトは二つの包含区間 XX と YY を関係付ける。これらは順序ではなく関係である。重なり合う包含区間では definitely_lt(X, Y) と definitely_lt(Y, X) の両方が偽になる。これらは点の組 x∈Xx \in X、y∈Yy \in Y のすべてについての主張として読むこと。このパッケージはインスタンスを提供せず、区間やボールのバックエンドが実装する。下記の区間についての式は設計ページで導出する。

トレイト成り立つ条件区間 X=[a,b]X = [a, b]、Y=[c,d]Y = [c, d] の場合
ContainsY⊆XY \subseteq Xa≤ca \le c かつ d≤bd \le b
OverlapsX∩Y≠∅X \cap Y \ne \emptyseta≤da \le d かつ c≤bc \le b
DefinitelyLtすべての x∈Xx \in X、y∈Yy \in Y について x<yx < yb<cb < c
DefinitelyLeすべての x∈Xx \in X、y∈Yy \in Y について x≤yx \le yb≤cb \le c
MaybeEqある x∈Xx \in X、y∈Yy \in Y について x=yx = ya≤da \le d かつ c≤bc \le b

Contains

Contains は一つ目の包含区間が二つ目を含むかどうかを判定する。

pub(open) trait Contains {
  fn contains(Self, Self) -> Bool
}

Overlaps

Overlaps は二つの包含区間が共通の点を持つかどうかを判定する。

pub(open) trait Overlaps {
  fn overlaps(Self, Self) -> Bool
}

DefinitelyLt

DefinitelyLt は一つ目の包含区間のすべての点が二つ目のすべての点より小さいかどうかを判定する。

pub(open) trait DefinitelyLt {
  fn definitely_lt(Self, Self) -> Bool
}

DefinitelyLe

DefinitelyLe は一つ目の包含区間のすべての点が二つ目のすべての点以下かどうかを判定する。

pub(open) trait DefinitelyLe {
  fn definitely_le(Self, Self) -> Bool
}

MaybeEq

MaybeEq は二つの包含区間が同じ数を表しうるかどうかを判定する。

pub(open) trait MaybeEq {
  fn maybe_eq(Self, Self) -> Bool
}

実数の包含区間では maybe_eq は overlaps と一致する。このトレイトは、汎用コードが意図した問いをそのまま書けるようにするために存在する。

struct Iv {
  lo : Double
  hi : Double
}

impl @lf_arith.Contains for Iv with contains(x, y) { x.lo <= y.lo && y.hi <= x.hi }

impl @lf_arith.DefinitelyLt for Iv with definitely_lt(x, y) { x.hi < y.lo }

impl @lf_arith.DefinitelyLe for Iv with definitely_le(x, y) { x.hi <= y.lo }

impl @lf_arith.MaybeEq for Iv with maybe_eq(x, y) { x.lo <= y.hi && y.lo <= x.hi }

test "interval relations" {
  let x = Iv::{ lo: 1.0, hi: 2.0 }
  let y = Iv::{ lo: 1.5, hi: 3.0 }
  let z = Iv::{ lo: 2.5, hi: 3.0 }
  inspect(@lf_arith.DefinitelyLt::definitely_lt(x, z), content="true")
  inspect(@lf_arith.DefinitelyLt::definitely_lt(x, y), content="false")
  inspect(@lf_arith.DefinitelyLt::definitely_lt(y, x), content="false")
  inspect(@lf_arith.MaybeEq::maybe_eq(x, y), content="true")
  inspect(@lf_arith.Contains::contains(y, z), content="true")
  inspect(@lf_arith.DefinitelyLe::definitely_le(x, Iv::{ lo: 2.0, hi: 4.0 }), content="true")
}

提供されるインスタンス

トレイトFloat, Double整数と BigInt
Sqrt, Cbrt, Radical, Exponential, Logarithmic, Trigonometric, InverseTrigonometric, Hyperbolic, InverseHyperbolic, Constantsありなし
PowerありInt, Int16, Int64, UInt, UInt16, UInt64, BigInt
SqrtChecked, DivChecked, CompareChecked, PowNatChecked, PowIntCheckedありなし
ParseCheckedなしなし
AddContextual, SubContextual, MulContextual, DivContextual, AbsContextual, SqrtContextual, ExpContextual, IntegralContextual, AdjacentContextual, NumericFormatContextualありなし
ConstantsContextual, HyperbolicContextualなしなし
Contains, Overlaps, DefinitelyLt, DefinitelyLe, MaybeEqなしなし

Float と Double の初等関数は Kaida-Amethyst/math から来ており、正しい丸めは保証されない。

非推奨

MoonBit 0.10 より前は、型にトレイトを実装すると、そのメソッドが暗黙にドット記法で呼べるようになっていた。これらの暗黙のメソッド形式はパッケージの公開型で今も呼べるが、非推奨であり、インターフェースファイルからは隠されている:

非推奨の形式型代替
x.not_equal(y)等価性に挙げたすべての型x != y
x.to_repr()FpClass, ArithmeticDiagnostics, ArithmeticOutcomeRepr(x) または @debug.to_string(x)