internal 教程

本页面向数值核心的维护者,介绍 internal 中的辅助函数如何组合使用:将十进制系数舍入到指定精度、将十进制字面量解析为精确的组成部分,以及编写一个对舍入结果进行认证的细化循环。该包是 Luna-Flow/floating 的内部包;这些示例是为模块内部代码编写的,不会针对已发布的包进行编译。

快速入门

在模块内部导入该包(字面量还需导入 bigint):

import {
  "Luna-Flow/floating/internal",
  "moonbitlang/core/bigint",
}

以两种模式将 2/32/3 舍入为整数:

///|
test "rounded quotient" {
  let mode_even = @lf_arith.RoundingMode::ToNearestEven
  let mode_down = @lf_arith.RoundingMode::TowardZero
  inspect(@internal.round_positive_div(2N, 3N, false, mode_even), content="1")
  inspect(@internal.round_positive_div(2N, 3N, false, mode_down), content="0")
}

round_positive_div 分别接收量值 n/dn/d 和真实商的符号,因为对于负数,定向舍入模式对量值的舍入方向不同。

日常任务

将十进制系数舍入到 p 位数字

位数多于 pp 的十进制数 c⋅10qc \cdot 10^{q} 的舍入方式是:将系数除以 10k10^{k}(其中 k=digits(c)−pk = \text{digits}(c) - p),并把 kk 加到指数上:

///|
fn round_coefficient(
  coefficient : BigInt,
  exponent : Int,
  precision : Int,
  mode : @lf_arith.RoundingMode,
) -> (BigInt, Int) {
  let negative = coefficient < 0N
  let magnitude = @internal.abs_bigint(coefficient)
  let excess = @internal.digits10(magnitude) - precision
  if excess <= 0 {
    return (coefficient, exponent)
  }
  let rounded = @internal.round_positive_div(
    magnitude,
    @internal.pow10(excess),
    negative,
    mode,
  )
  (if negative { -rounded } else { rounded }, exponent + excess)
}

///|
test "round to three digits" {
  let (c, q) = round_coefficient(-123456N, -3, 3, @lf_arith.RoundingMode::TowardNegative)
  inspect(c, content="-124")
  inspect(q, content="0")
}

进位可能产生 10p10^{p}(例如 999.6→1000999.6 \to 1000);实际的核心会对这种情况重新规格化,此示意代码省略了这一步。

将字面量解析为精确的组成部分

split_decimal_string 为十进制解析器完成词法层面的工作;对结果去除尾随零即可得到规范系数:

///|
test "parse then trim" {
  guard @internal.split_decimal_string("-0.012500e2") is Some((negative, digits, exponent)) else {
    fail("expected a literal")
  }
  let coefficient = BigInt::from_string(digits)
  let (c, q, dropped) = @internal.trim_trailing_decimal_zeros(coefficient, exponent)
  inspect(negative, content="true")
  inspect(c, content="125")
  inspect(q, content="-2")
  inspect(dropped, content="2")
}

因此 −0.012500×102=−125×10−2-0.012500 \times 10^{2} = -125 \times 10^{-2}。

用细化循环认证舍入

经认证的初等函数遵循 Ziv 策略:在某个工作精度下计算一个包络,只有当包络两端舍入到同一个数时才接受舍入结果;否则提高精度。预算限定了循环的次数。这里的“函数”是精确比值 n/dn/d,向下舍入到 target 个小数位(二进制):

///|
fn floor_ratio_certified(
  n : BigInt,
  d : BigInt,
  target : Int,
) -> Result[@internal.CertifiedDyadic, @lf_arith.ArithmeticError] {
  let mut budget = @internal.CertifiedRefinementBudget::new(target + 2)
  while budget.available() {
    let enclosure = match
      @internal.certified_dyadic_fraction(n, d, budget.precision()) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let low = enclosure.lower().round_down(target)
    let high = enclosure.upper().round_down(target)
    if low.compare(high) == 0 {
      return Ok(low)
    }
    budget = budget.next()
  }
  Err(
    @internal.certified_failure(
      "floor_ratio",
      @lf_arith.CertificationStage::TargetRounding,
      @lf_arith.CertificationFailureReason::RefinementBudgetExhausted,
      target,
      budget,
    ),
  )
}

///|
test "certified floor of 1/3" {
  let third = floor_ratio_certified(1N, 3N, 8).unwrap()
  inspect(third.numerator(), content="85")
  inspect(third.scale(), content="8")
}

深入了解

  • bin_float、decimal、decimal_gda 和 ball_float 以目标精度加上保护余量来创建细化预算,并在预算耗尽时报告 certified_failure;经认证的初等函数参见 bin_float 设计。
  • semantic 使用 ExactRat 作为有限二进制数和十进制数的规范精确值。
  • consistency 包以 BigInt 预言为参照测试这些辅助函数。
  • 在包含该模块的工作区中运行该包的测试:moon test -p Luna-Flow/floating/internal。

常见陷阱

  • 带符号的输入。 round_positive_div 遇到负的分子会中止;请分别传入量值和符号。
  • s≤0s \le 0 时的 round_shift 原样返回输入;它从不左移。
  • 零会丢失其指数。 对于零系数,remove_factor2、remove_factor10 和 trim_trailing_decimal_zeros 返回指数 0。如果零的同值类(cohort)很重要,请自行保存原始指数。
  • 未规格化的二进有理数。 不同尺度的 CertifiedDyadic 值可能在数值上相等;请用 compare 比较,而不是逐字段比较。

后续步骤