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)

对于位于两个相邻可表示值之间的实数 xx,即 a<x<ba < x < b:

模式返回值
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后端未实现的运算或上下文
UnorderedComparison涉及 NaN 等无序值的比较
CertificationFailure基于证明的后端无法认证其结果;附带详细信息

该枚举是 pub 而非 pub(all):可以对它做模式匹配,但错误需要通过下面的 ArithmeticError 构造函数来构造。

ArithmeticError

ArithmeticError 是所有检查 trait 和上下文 trait 返回的结构化错误。

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 值会使两个结果不相等。

非检查能力 trait

每个非检查 trait 都是一项能力,其方法返回 Self。trait 不规定定义域:定义域之外的行为(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)、以 2 为底的对数(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, Double即 C 的 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(结果精确,因为乘以 2 只改变指数),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")
}

检查能力 trait

检查 trait 返回 Result[_, ArithmeticError],因此被拒绝的运算是调用方必须处理的值。可能依赖精度的方法接受一个 ArithmeticContext;内置的 Float 和 Double 实例接受该参数但不读取它。

SqrtChecked

SqrtChecked 计算平方根,或报告定义域错误。

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

定义域由各实例规定;trait 不假定存在序。对于 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/\, 0DivisionByZero (division by zero)
其他情况Ok(x / y),按 IEEE 规则传播 NaN
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=±0x = \pm 0 时,x−nx^{-n} 为 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。本包不提供任何实例;解析属于具有文本格式的后端,例如十进制类型。

上下文能力 trait

上下文 trait 返回 Result[ArithmeticOutcome[Self], ArithmeticError]。Err 表示运算被拒绝;Ok 携带值以及后端在计算过程中检测到的诊断。

AddContextual, SubContextual, MulContextual, DivContextual

这四个 trait 是 +、-、* 和 / 的上下文形式。

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 是机器 epsilon ε=β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")
}

包络关系

包络是这样一种值:它代表一个未知实数,并已知该实数位于某个集合中,例如区间或球。这五个 trait 描述两个包络 XX 与 YY 之间的关系。它们是关系而不是序:对于相互重叠的包络,definitely_lt(X, Y) 和 definitely_lt(Y, X) 都为假。应把它们理解为关于每一对点 x∈Xx \in X、y∈Yy \in Y 的陈述。本包不提供任何实例;由区间后端和球后端实现它们。设计页推导了下面的区间公式。

Trait成立条件对区间 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 一致;该 trait 的存在是为了让泛型代码能按其本意提问。

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")
}

内置实例

TraitFloat, 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 之前,为某个类型实现 trait 会隐式地使其方法可以用点语法调用。这些隐式方法形式在本包的公开类型上仍可调用,但已弃用,并且不出现在接口文件中:

弃用形式类型替代写法
x.not_equal(y)相等性一节列出的所有类型x != y
x.to_repr()FpClass, ArithmeticDiagnostics, ArithmeticOutcomeRepr(x) 或 @debug.to_string(x)