def 教程
本教程展示如何编写能同时适用于 floating 所有数值类型的代码:通过 Floating trait 检视值,用泛型谓词检验其类别,把 IEEE 比较结果读作 PartialOrder,并复用 def 重新导出的算术上下文与错误类型。def 本身不做任何算术;算术由具体的包(bin_float、decimal、decimal_gda、ball_float)完成。该 trait 背后的定律见设计页面。
快速入门
添加模块,并在你所用的表示之外再导入 def:
moon add Luna-Flow/floating@0.8.0
import {
"Luna-Flow/floating/def",
"Luna-Flow/floating/bin_float",
}
最小的实用程序询问一个值是什么:
///|
test "quick start: observe a value" {
let x = @bin_float.BinFloat::from_double(-2.5)
inspect(@def.is_finite(x), content="true")
inspect(@def.Floating::sign(x) == @def.Sign::Negative, content="true")
inspect(@def.Floating::precision(x), content="53")
}
日常任务
为所有表示编写同一个函数
以 F : @def.Floating 为约束的函数可以接受二进制、IEEE 十进制、GDA 十进制和区间值。请以限定形式 @def.Floating::classify(x) 调用 trait 方法,因为 MoonBit 不再把 trait 方法变成类型参数的点方法。
///|
fn[F : @def.Floating] summary(x : F) -> String {
if @def.is_nan(x) {
return "nan"
}
let sign = match @def.Floating::sign(x) {
Negative => "negative"
Zero => "zero"
Positive => "positive"
}
let kind = if @def.is_infinite(x) { "infinite" } else { "finite" }
"\{kind} \{sign} at precision \{@def.Floating::precision(x)}"
}
///|
test "one summary for four representations" {
inspect(
summary(@bin_float.BinFloat::from_int(7)),
content="finite positive at precision 53",
)
inspect(
summary(@decimal.Decimal::from_string("-0.00").unwrap()),
content="finite zero at precision 34",
)
inspect(
summary(@decimal_gda.Decimal::from_string("-Infinity").unwrap()),
content="infinite negative at precision 34",
)
inspect(summary(@ball_float.BallFloat::empty()), content="nan")
}
区间的情形说明了为什么要先检验 NaN:空区间被分类为 NaN,而 BallFloat::sign 在其上会中止。
泛型地改变精度
with_precision 舍入到所要求的有效数字位数,位数以值自身的基数计:二进制为位,十进制为十进制数字:
///|
fn[F : @def.Floating] three_digits(x : F) -> F {
@def.Floating::with_precision(x, 3, @lf_arith.RoundingMode::ToNearestEven)
}
///|
test "round to three significant digits of the radix" {
let d = three_digits(@decimal.Decimal::from_string("3.14159").unwrap())
inspect(d.to_string(), content="3.14")
let b = three_digits(@bin_float.BinFloat::from_int(11))
inspect(b.to_string(), content="3p2")
}
十一的二进制是 1011;三位有效位把它舍入为 1100,to_string 将其打印为 。
读取 IEEE 比较结果
遵循 IEEE 754 的比较返回一个 PartialOrder,其第四个值 Unordered 表示有 NaN 操作数:
///|
fn relation(a : @bin_float.BinFloat, b : @bin_float.BinFloat) -> String {
match a.compare_quiet(b).0 {
Less => "less"
Equal => "equal"
Greater => "greater"
Unordered => "unordered"
}
}
///|
test "four-way comparison" {
let one = @bin_float.BinFloat::from_int(1)
let two = @bin_float.BinFloat::from_int(2)
inspect(relation(one, two), content="less")
inspect(relation(@bin_float.BinFloat::nan(), @bin_float.BinFloat::nan()), content="unordered")
inspect(
relation(@bin_float.BinFloat::from_double(-0.0), @bin_float.BinFloat::zero()),
content="equal",
)
}
比较表示之前先规范化
值相同的十进制数,其存储的指数可能不同(1.5 和 1.500 属于同一个同值类)。normalized 选取规范成员,从而使打印形式一致:
///|
test "normalize a decimal cohort" {
let a = @decimal.Decimal::from_string("1.500").unwrap()
let b = @decimal.Decimal::from_string("1.5").unwrap()
inspect(a.to_string(), content="1.500")
inspect(
@def.Floating::normalized(a).to_string() ==
@def.Floating::normalized(b).to_string(),
content="true",
)
}
深入了解
使用重新导出的算术类型
@def.ArithmeticContext、@def.ArithmeticError、@def.RoundingMode 以及其他别名都是 Luna-Flow/arithmetic 的类型。只需要这套词汇的代码可以导入 def 而不是 arithmetic:
///|
fn describe_error(e : @def.ArithmeticError) -> String {
if e.is_division_by_zero() {
"division by zero: " + e.message
} else if e.is_domain_error() {
"domain error: " + e.message
} else {
"other: " + e.message
}
}
///|
test "handle a checked result through def" {
let result = @bin_float.BinFloat::from_int(1).div_checked(
@bin_float.BinFloat::zero(),
)
match result {
Ok(_) => fail("expected an error")
Err(e) => inspect(describe_error(e), content="division by zero: division by zero")
}
}
为自己的类型实现 Floating
该 trait 是开放的。包装类型可以委托给已有的实现;但它必须遵守设计页面列出的定律(例如,normalized 不得改变值):
///|
struct Measured {
value : @bin_float.BinFloat
unit : String
}
///|
impl @def.Floating for Measured with classify(self) {
@def.Floating::classify(self.value)
}
///|
impl @def.Floating for Measured with sign(self) {
@def.Floating::sign(self.value)
}
///|
impl @def.Floating for Measured with precision(self) {
@def.Floating::precision(self.value)
}
///|
impl @def.Floating for Measured with with_precision(self, precision, mode) {
{ ..self, value: @def.Floating::with_precision(self.value, precision, mode) }
}
///|
impl @def.Floating for Measured with normalized(self) {
{ ..self, value: @def.Floating::normalized(self.value) }
}
///|
test "a user type joins the generic code" {
let m = { value: @bin_float.BinFloat::from_int(-4), unit: "m" }
inspect(summary(m), content="finite negative at precision 53")
inspect(@def.is_zero(m), content="false")
let coarse = @def.Floating::with_precision(m, 1, @lf_arith.RoundingMode::TowardZero)
inspect(coarse.unit, content="m")
}
常见陷阱
Sign::Zero并不意味着“值为零”。标量类型对 NaN 也返回它,任何包含零的区间也返回它。请先检验@def.is_nan;对区间请使用BallFloat::contains_zero或其界。- 对
BallFloat而言,@def.is_zero在 上为真:该谓词通过sign定义,而它对区间的含义是“包含零”。 Sign无法区分 和 。当符号位重要时,请使用具体的包(BinFloat::is_negative_zero、Decimal::is_signed)。PartialOrder不是可以用来排序的序:Unordered破坏了三分律。排序请使用具体包提供的全序(BinFloat::total_order、Decimal::compare_total)。with_precision不报告标志,也忽略指数界限。当舍入必须可观测时,请使用具体包的*_ctx运算。with_precision(x, 0, mode)不是错误:精度会被钳制为 1。
后续步骤
def设计:trait 定律以及PartialOrder的序理论。defAPI:每个条目及其签名。semantic教程:精确比较不同表示的值。- 本仓库的共享词汇见数值语义指南。