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 位精度包络 ln⁡2\ln 2:

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

以最简形式打印的上界是 6243314768165360⋅2−536243314768165360 \cdot 2^{-53},因此两个界是相邻的 53 位数,ln⁡2\ln 2 位于它们之间。

日常任务

其余示例用下面这个辅助函数打印结果;有界区间以精确二进制记法打印为 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)
  }
}

面积位于 [2,7.5][2, 7.5],即两个范围之积。

“没有信息”并不是错误

区间运算遵循集合语义:函数定义域之外的点被舍弃,而无法给出任何信息的运算返回整条实轴。这些都算成功,因为它们是正确的包络:

///|
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) 不包含 100001100001。对于大整数请传入 precision=53(或更大)。
  • 空集和整条实轴都算成功。 请对最终区间检验 is_empty / is_entire,或加入一个 bind 检查。
  • 没有装饰或标志。 该包装器既不保留 IEEE 1788 装饰,也不保留 BallFlags;需要它们时请使用 ball_float 的装饰 API 和 contextual API。
  • 只有第一个错误会保留下来,所有 checked 包装器都是如此。
  • [+inf, +inf] 会被拒绝。 IEEE 1788 区间是实轴的子集;请改用 whole() 或半无穷区间。
  • flat_map 已弃用。 请使用 bind。

后续步骤