ball_float_checked 教程
本教程展示如何运行这样一种区间计算:无效输入和无法认证的步骤会作为错误报告,而每个有效结果仍是有保证的包络。你将用带验证的构造器构造区间,在 BallFloatResult 中串联区间算术和初等函数,用 bind 加入自己的检查,并在最后一次性读取结果。算术来自 ball_float;设计页面解释了哪些结果属于错误;API 参考列出了所有方法。
快速入门
moon add Luna-Flow/floating@0.8.0
import {
"Luna-Flow/floating/bin_float",
"Luna-Flow/floating/ball_float_checked",
}
以 53 位精度包络 :
///|
test "quick start: an enclosure of ln 2" {
let r = @ball_float_checked.BallFloatResult::from_int(2, precision=53).ln()
match r.result() {
Ok(x) => {
inspect(x.lower_bound().to_string(), content="6243314768165359p-53")
inspect(x.upper_bound().to_string(), content="390207173010335p-49")
}
Err(e) => fail(e.message)
}
}
以最简形式打印的上界是 ,因此两个界是相邻的 53 位数, 位于它们之间。
日常任务
其余示例用下面这个辅助函数打印结果;有界区间以精确二进制记法打印为 centre +/- radius:
///|
fn show(r : @ball_float_checked.BallFloatResult) -> String {
match r.result() {
Ok(v) => v.to_string()
Err(e) => "error: " + e.message
}
}
在边界处验证测量值
输入数据通过 from_bounds、from_double 或 exact 进入。上下界颠倒和非有限的来源会在任何计算运行之前就成为错误:
///|
fn reading(lo : Double, hi : Double) -> @ball_float_checked.BallFloatResult {
@ball_float_checked.BallFloatResult::from_bounds(
@bin_float.BinFloat::from_double(lo),
@bin_float.BinFloat::from_double(hi),
)
}
///|
test "validated readings" {
inspect(show(reading(1.0, 3.0)), content="1p1 +/- 1p0")
inspect(show(reading(3.0, 1.0)), content="error: ball lower bound must not exceed upper bound")
inspect(
show(reading(0.0 / 0.0, 1.0)),
content="error: ball bounds must not be NaN",
)
}
让不确定性在公式中传播
运算符作用于包装器。结果包络了输入落在各区间内时该公式可能取到的每个值:
///|
test "area of an uncertain rectangle" {
let width = reading(1.0, 3.0)
let height = reading(2.0, 2.5)
let area = width * height
match area.result() {
Ok(x) => {
inspect(x.lower_bound().to_string(), content="1p1")
inspect(x.upper_bound().to_string(), content="15p-1")
}
Err(e) => fail(e.message)
}
}
面积位于 ,即两个范围之积。
“没有信息”并不是错误
区间运算遵循集合语义:函数定义域之外的点被舍弃,而无法给出任何信息的运算返回整条实轴。这些都算成功,因为它们是正确的包络:
///|
test "empty and whole results are successes" {
let negative = @ball_float_checked.BallFloatResult::from_int(-2)
inspect(show(negative.ln()), content="[empty]")
inspect(negative.ln().is_ok(), content="true")
let one = @ball_float_checked.BallFloatResult::from_int(1)
inspect(show(one / reading(-1.0, 1.0)), content="[-inf, inf]")
}
如果在你的应用中空结果或无界结果意味着失败,就用 bind 明确表达。
把应用规则变成错误
///|
fn require_nonempty(
x : @ball_float.BallFloat,
) -> @ball_float_checked.BallFloatResult {
if x.is_empty() {
@ball_float_checked.BallFloatResult::err(
@lf_arith.ArithmeticError::domain_error("no admissible value"),
)
} else {
@ball_float_checked.BallFloatResult::ok(x)
}
}
///|
test "reject empty enclosures" {
let r = @ball_float_checked.BallFloatResult::from_int(-2).ln().bind(require_nonempty)
inspect(show(r), content="error: no admissible value")
let ok = reading(1.0, 3.0).ln().bind(require_nonempty)
inspect(ok.is_ok(), content="true")
}
深入了解
认证失败
初等函数以经过认证的误差界求值。当库无法在预算内认证一个包络时,它会报告带有详情记录的 CertificationFailure,而不是返回错误的或不必要地宽的区间:
///|
test "certification failure carries a detail record" {
let huge = @ball_float_checked.BallFloatResult::exact(
@bin_float.BinFloat::make(@bin_float.BinCoeff::one(), 70000, 53),
)
match huge.sin().error() {
Some(e) => {
inspect(e.is_certification_failure(), content="true")
inspect(e.certification_failure_detail().unwrap().operation(), content="sin")
}
None => fail("expected a failure")
}
}
通过 arithmetic trait 求幂
pow_nat 和 pow_int 经由 Luna-Flow/arithmetic 的 PowNatChecked 和 PowIntChecked trait 实现,BallFloat 通过计算整个区间的幂来实现它们。对于包含零的区间,这比重复相乘更紧,因为重复相乘把两个因子视为相互独立:
///|
test "a power is tighter than a product" {
let x = reading(-1.0, 1.0)
match (x.pow_int(2).result(), (x * x).result()) {
(Ok(square), Ok(product)) => {
inspect(square.lower_bound().to_string(), content="0")
inspect(product.lower_bound().to_string(), content="-1p0")
}
_ => fail("unexpected error")
}
}
使用包络关系的泛型代码
BallFloat 实现了 Luna-Flow/arithmetic 的包络关系(Contains、DefinitelyLt……)。在流水线末尾取出区间并对其进行检验:
///|
test "decide with an enclosure" {
match reading(1.0, 3.0).rootn(2).result() {
Ok(root) => {
let two = @ball_float.BallFloat::from_int(2)
inspect(@lf_arith.DefinitelyLt::definitely_lt(root, two), content="true")
}
Err(e) => fail(e.message)
}
}
常见陷阱
- 默认精度较小。
from_int和from_coefficient默认使用 16 位。在当前分支上,需要更多位的整数会在被包络之前就舍入到最近值,因此from_int(100001)不包含 。对于大整数请传入precision=53(或更大)。 - 空集和整条实轴都算成功。 请对最终区间检验
is_empty/is_entire,或加入一个bind检查。 - 没有装饰或标志。 该包装器既不保留 IEEE 1788 装饰,也不保留
BallFlags;需要它们时请使用ball_float的装饰 API 和 contextual API。 - 只有第一个错误会保留下来,所有 checked 包装器都是如此。
[+inf, +inf]会被拒绝。 IEEE 1788 区间是实轴的子集;请改用whole()或半无穷区间。flat_map已弃用。 请使用bind。
后续步骤
ball_float_checked设计:错误与包络的区别,以及组合律。ball_float_checkedAPI:所有方法。ball_float教程:装饰、上下文以及区间运算本身。semantic教程:用精确参考值检查包络。