arithmetic 教程
本教程演示如何在你自己的泛型辅助函数中使用 arithmetic 的标量运算 trait:绝对值、近似比较,以及把非法输入转为可处理的值的受检除法、平方根与比较。背景知识见 arithmetic 设计。
快速上手
moon add Luna-Flow/linear-algebra@0.5.0
moon add Luna-Flow/arithmetic@0.2.2
///|
import {
"Luna-Flow/linear-algebra/arithmetic" @la_arithmetic,
"Luna-Flow/arithmetic" @lf_arith,
}
一个泛型的“最大模”辅助函数需要 Abs 和 Compare:
///|
fn[T : @la_arithmetic.Abs + Compare] arith_tut_max_abs(xs : Array[T]) -> T? {
let mut best : T? = None
for x in xs {
let a = @la_arithmetic.Abs::abs(x)
best = match best {
Some(b) if b >= a => Some(b)
_ => Some(a)
}
}
best
}
///|
test "largest magnitude" {
debug_inspect(arith_tut_max_abs([3, -7, 5]), content="Some(7)")
debug_inspect(arith_tut_max_abs([0.5, -0.25]), content="Some(0.5)")
}
日常任务
归一化向量时避免除以零
CheckedDiv 把零除数报告为错误,而不是产生无穷大:
///|
fn arith_tut_normalize(
xs : Array[Double],
ctx : @lf_arith.ArithmeticContext,
) -> Result[Array[Double], @lf_arith.ArithmeticError] {
let mut sum = 0.0
for x in xs {
sum = sum + x.abs()
}
let out = []
for x in xs {
match @la_arithmetic.CheckedDiv::checked_div(x, sum, ctx) {
Ok(v) => out.push(v)
Err(e) => return Err(e)
}
}
Ok(out)
}
///|
test "normalize by the 1-norm" {
let ctx = @lf_arith.ArithmeticContext::new(53)
debug_inspect(
arith_tut_normalize([1.0, 3.0], ctx).unwrap(),
content="[0.25, 0.75]",
)
inspect(arith_tut_normalize([0.0, 0.0], ctx) is Err(_), content="true")
}
对全零输入,第一次除法是 ,会以 kind DomainError 报告。
只在定义域内开平方
///|
fn arith_tut_std_from_variance(
variance : Double,
) -> Result[Double, @lf_arith.ArithmeticError] {
@la_arithmetic.CheckedSqrt::checked_sqrt(
variance,
@lf_arith.ArithmeticContext::new(53),
)
}
///|
test "square root of a variance" {
inspect(arith_tut_std_from_variance(2.25).unwrap(), content="1.5")
match arith_tut_std_from_variance(-1.0e-18) {
Err(e) => inspect(e.is_domain_error(), content="true")
Ok(_) => fail("negative variance must be rejected")
}
}
略为负的方差是单遍公式中典型的舍入产物;受检调用让你自己决定如何处理它。
对可能含 NaN 的数据排序
checked_compare 在遇到 NaN 时失败,因此可以在排序之前检测出无序数据:
///|
fn arith_tut_all_ordered(xs : Array[Double]) -> Bool {
for x in xs {
if @la_arithmetic.CheckedCompare::checked_compare(x, x) is Err(_) {
return false
}
}
true
}
///|
test "detect NaN before sorting" {
inspect(arith_tut_all_ordered([2.0, 1.0]), content="true")
inspect(arith_tut_all_ordered([2.0, 0.0 / 0.0]), content="false")
}
近似比较结果
ApproxEq 适用于量级为一的值,例如归一化向量的元素:
///|
fn[T : @la_arithmetic.ApproxEq] arith_tut_all_close(
xs : Array[T],
ys : Array[T],
) -> Bool {
if xs.length() != ys.length() {
return false
}
for i in 0..<xs.length() {
if !@la_arithmetic.ApproxEq::approx_eq(xs[i], ys[i]) {
return false
}
}
true
}
///|
test "approximate comparison of computed values" {
inspect(arith_tut_all_close([0.1 + 0.2, 1.0], [0.3, 1.0]), content="true")
inspect(0.1 + 0.2 == 0.3, content="false")
}
进一步了解
你自己的标量类型。 所有 trait 都是 pub(open)。十进制或有理数类型可以实现 Abs、ApproxEq(对精确类型直接用 == 即可)以及受检 trait;之后它就能配合所有针对这些 trait 编写的辅助函数使用。
矩阵代码中的错误。 矩阵包用 LinearAlgebraError::arithmetic_failure 报告标量失败,它包装一个 ArithmeticError。混合了矩阵与标量步骤的辅助函数可以用该构造函数进行转换;参见错误教程。
上游 trait。 对超越函数和带上下文的算术,请直接使用 Luna-Flow/arithmetic;本包只重新导出 API 页面上列出的名称。
常见陷阱
- 绝对容差用错了量级。 对 附近的值,
approx_eq实际上就是严格相等;对 附近的值,它接受一切。请对数据做缩放,或使用自己的相对规则。 - 串联近似比较。 与 并不蕴含 。
- 期望上下文改变
Double结果。 对二进制浮点数,ArithmeticContext的精度会被忽略。 - 对
Int.MIN_VALUE使用Abs。 结果会回绕,仍为负数。
后续步骤
- arithmetic API:每个 trait 的确切规则。
- arithmetic 设计:为什么
approx_eq不是等价关系。 - mutable 教程:依赖
Sqrt和Tolerance的矩阵例程。 - Luna-Flow/arithmetic:完整的标量运算库。