docs(objects): explain numeric representations and operation budgets

This commit is contained in:
2026-11-15 17:30:28 +08:00
parent fce5ccf883
commit c25cb6756e
+109
View File
@@ -1,3 +1,5 @@
//! 编译器字面量、VM 算术比较与宿主数值转换共用的表示;构造保存与运算预算分开。
//! Eq 比较存储表示,numeric_compare 比较数学值;Rust 数值载荷不持有托管对象边。
//! Lossless number representations. Arithmetic and numeric equivalence are
//! deliberately separate from representation equality; real operations preserve
//! exact values unless a binary-float operand explicitly selects float mode.
@@ -9,17 +11,23 @@ use crate::{CompareOp, ObjectPayload, ObjectRef, VmError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndorInteger {
/// 规范化整数符号,零为 false。
negative: bool,
/// 无符号、无前导零的数字串;零保留单个 0。
digits: String,
}
impl EndorInteger {
/// Decimal digits of any length, limited only by available memory.
/// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。
pub fn parse(text: &str) -> Result<Self, VmError> {
// 拆符号后校验数字,空串或只有符号均非法。
let (negative, digits) = split_sign(text);
// 只允许非空 ASCII 数字,不接受空白、下划线或进制前缀。
if !is_digits(digits) {
return Err(VmError::InvalidArgument("invalid integer"));
}
// 去前导零,全零统一保存单个正零。
let digits = digits.trim_start_matches('0');
Ok(Self {
negative: negative && !digits.is_empty(),
@@ -27,21 +35,27 @@ impl EndorInteger {
})
}
/// 宿主/转换借用规范化整数绝对值数字串。
pub fn digits(&self) -> &str {
&self.digits
}
/// 查询整数符号,构造已消除负零。
pub fn is_negative(&self) -> bool {
self.negative
}
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
pub fn is_zero(&self) -> bool {
self.digits == "0"
}
/// 算术快路径尝试收窄整数,超范围返回 None,不截断。
pub fn to_i64(&self) -> Option<i64> {
// 先读 u64 幅值以容纳 i64::MIN 的绝对值,再检查有符号范围。
let magnitude = self.digits.parse::<u64>().ok()?;
if self.negative {
// 最小负数的幅值比最大正数多一,必须单独处理。
if magnitude == (i64::MAX as u64) + 1 {
Some(i64::MIN)
} else {
@@ -54,6 +68,7 @@ impl EndorInteger {
}
impl fmt::Display for EndorInteger {
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.negative {
f.write_str("-")?;
@@ -64,25 +79,34 @@ impl fmt::Display for EndorInteger {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndorDecimal {
/// 已验证但未规范化的原十进制字面量。
literal: String,
/// 缓存尾数是否全零。
zero: bool,
}
impl EndorDecimal {
/// Retains all digits, trailing zeroes, sign, and an arbitrarily long
/// decimal exponent. No intermediate binary floating-point conversion.
/// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。
pub fn parse(text: &str) -> Result<Self, VmError> {
// 校验时去符号,但最终仍保存完整 text,不丢失显式正号和负零。
let (_, unsigned) = split_sign(text);
// 指数最多一段,可带符号;解析不立刻收窄指数到机器整数。
let mut parts = unsigned.split(['e', 'E']);
// 尾数后续允许一个点和至少一个数字。
let mantissa = parts.next().unwrap_or_default();
// 指数必须有数字且不得有第二个指数段。
if let Some(exponent) = parts.next()
&& (!is_digits(split_sign(exponent).1) || parts.next().is_some())
{
return Err(VmError::InvalidArgument("invalid decimal exponent"));
}
// 分别记录已有小数点、数字数量与尾数是否全零。
let mut decimal_point = false;
let mut digits = 0_usize;
let mut zero = true;
// 按 ASCII 字节扫描,第二个点或其他字符都拒绝。
for byte in mantissa.bytes() {
match byte {
b'.' if !decimal_point => decimal_point = true,
@@ -93,6 +117,7 @@ impl EndorDecimal {
_ => return Err(VmError::InvalidArgument("invalid decimal")),
}
}
// 允许 .5 和 5.,但不允许只有点或空尾数。
if digits == 0 {
return Err(VmError::InvalidArgument("decimal needs digits"));
}
@@ -102,16 +127,19 @@ impl EndorDecimal {
})
}
/// 显示和精确转换借用原字面量,保留符号、指数及尾零。
pub fn literal(&self) -> &str {
&self.literal
}
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
pub fn is_zero(&self) -> bool {
self.zero
}
}
impl fmt::Display for EndorDecimal {
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.literal)
}
@@ -119,21 +147,27 @@ impl fmt::Display for EndorDecimal {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndorRational {
/// 带符号分子,不要求已约分。
numerator: EndorInteger,
/// 构造保证正且非零的分母。
denominator: EndorInteger,
}
impl EndorRational {
/// Normalizes denominator sign, without reducing or rounding the fraction.
/// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。
pub fn new(
mut numerator: EndorInteger,
mut denominator: EndorInteger,
) -> Result<Self, VmError> {
// 构造时拒绝零分母,后续零判断只需看分子。
if denominator.is_zero() {
return Err(VmError::InvalidArgument("zero rational denominator"));
}
// 分母归正,同时翻分子符号;不执行约分。
if denominator.negative {
denominator.negative = false;
// 零分子保持正零,不引入负零。
if !numerator.is_zero() {
numerator.negative = !numerator.negative;
}
@@ -144,16 +178,19 @@ impl EndorRational {
})
}
/// 显示与精确转换借用分子表示。
pub fn numerator(&self) -> &EndorInteger {
&self.numerator
}
/// 显示与精确转换借用正且非零的分母表示。
pub fn denominator(&self) -> &EndorInteger {
&self.denominator
}
}
impl fmt::Display for EndorRational {
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.numerator, self.denominator)
}
@@ -170,6 +207,7 @@ pub enum EndorReal {
}
impl EndorReal {
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
pub fn is_zero(&self) -> bool {
match self {
Self::Integer(value) => value.is_zero(),
@@ -181,6 +219,7 @@ impl EndorReal {
}
impl fmt::Display for EndorReal {
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Integer(value) => value.fmt(f),
@@ -198,20 +237,25 @@ impl fmt::Display for EndorReal {
pub enum EndorNumber {
Real(EndorReal),
Complex {
/// 复数实部分量。
real: EndorReal,
/// 复数虚部分量。
imaginary: EndorReal,
},
}
impl EndorNumber {
/// 编译器/宿主构造整数实数表示,解析错误原样传播。
pub fn integer(text: &str) -> Result<Self, VmError> {
Ok(Self::Real(EndorReal::Integer(EndorInteger::parse(text)?)))
}
/// 编译器/宿主构造保留字面量的十进制实数。
pub fn decimal(text: &str) -> Result<Self, VmError> {
Ok(Self::Real(EndorReal::Decimal(EndorDecimal::parse(text)?)))
}
/// 宿主/运算结果从两段整数文本建立有理数,拒绝零分母。
pub fn rational(numerator: &str, denominator: &str) -> Result<Self, VmError> {
Ok(Self::Real(EndorReal::Rational(EndorRational::new(
EndorInteger::parse(numerator)?,
@@ -219,14 +263,17 @@ impl EndorNumber {
)?)))
}
/// 宿主显式选择二进制浮点,保存全部 IEEE 位。
pub fn binary_float(value: f64) -> Self {
Self::Real(EndorReal::BinaryFloat(value.to_bits()))
}
/// 组合两分量,不因虚部为零自动退化为实数;当前不支持复数算术。
pub fn complex(real: EndorReal, imaginary: EndorReal) -> Self {
Self::Complex { real, imaginary }
}
/// VM 算术/转换查询实数,Complex 即使虚部为零也返回 None。
pub fn as_real(&self) -> Option<&EndorReal> {
match self {
Self::Real(value) => Some(value),
@@ -234,6 +281,7 @@ impl EndorNumber {
}
}
/// VM 快路径仅接受整数变体,不自动转小数或可整除分数。
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::Real(EndorReal::Integer(value)) => value.to_i64(),
@@ -241,6 +289,7 @@ impl EndorNumber {
}
}
/// 数值真值/分母检查使用的零判断,具体实现按表示处理。
pub fn is_zero(&self) -> bool {
match self {
Self::Real(value) => value.is_zero(),
@@ -250,18 +299,21 @@ impl EndorNumber {
}
impl From<i64> for EndorNumber {
/// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。
fn from(value: i64) -> Self {
Self::integer(&value.to_string()).expect("i64 always has valid decimal digits")
}
}
impl From<f64> for EndorNumber {
/// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。
fn from(value: f64) -> Self {
Self::binary_float(value)
}
}
impl fmt::Display for EndorNumber {
/// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Real(value) => value.fmt(f),
@@ -272,45 +324,57 @@ impl fmt::Display for EndorNumber {
#[derive(Debug, Clone)]
pub struct EndorNumberObject {
/// 拥有数值表示,不含托管对象强边。
value: EndorNumber,
}
impl EndorNumberObject {
/// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。
pub fn new(value: EndorNumber) -> Self {
Self { value }
}
/// VM/宿主借用堆数值,不增加根或复制内容。
pub fn value(&self) -> &EndorNumber {
&self.value
}
}
impl ObjectPayload for EndorNumberObject {
/// 数值载荷不可变,不实现旧式原地赋值,允许只读局部重绑定。
fn reference_assignment_only(&self) -> bool {
true
}
/// Store 的 Rust 载荷下转型入口。
fn as_any(&self) -> &dyn Any {
self
}
/// Store 错误与诊断使用的载荷类别,不是名义类型。
fn kind_name(&self) -> &'static str {
"number"
}
/// Store 显示数值,不打印对象地址。
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
Ok(self.value.to_string())
}
/// Store 真值协议按非零判断,复数要求两分量都为零才为假。
fn is_true(&self) -> Result<bool, VmError> {
Ok(!self.value.is_zero())
}
/// 统一数值哈希尚未定义,拒绝以免数学相等而表示不同的键哈希不兼容。
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
Err(VmError::UnsupportedOperation("numeric hash"))
}
/// Store 对象比较协议,只在对方也是数值载荷时处理,否则返回 None。
fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result<Option<bool>, VmError> {
// 异类载荷不强行比较,交给 Store 的反向协议或身份回退。
if let Some(other) = other.as_any().downcast_ref::<Self>() {
// 复数目前连相等比较也不支持,明确报协议错误。
if self.value.as_real().is_none() || other.value.as_real().is_none() {
return Err(VmError::UnsupportedOperation("numeric comparison"));
}
@@ -320,7 +384,9 @@ impl ObjectPayload for EndorNumberObject {
}
}
/// 解析共用单个符号拆分,不去空白或重复符号。
fn split_sign(text: &str) -> (bool, &str) {
// 只剥离一个前导符号,剩余文本交给数字语法校验。
if let Some(rest) = text.strip_prefix('-') {
(true, rest)
} else {
@@ -328,6 +394,7 @@ fn split_sign(text: &str) -> (bool, &str) {
}
}
/// 解析共用非空 ASCII 数字检查,不接受 Unicode 数字。
fn is_digits(text: &str) -> bool {
!text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit())
}
@@ -343,6 +410,7 @@ pub enum ArithmeticOp {
Divide,
}
impl ArithmeticOp {
/// eval 解 BinaryArithmetic 的 0..3 运算编号,其他值返回 None。
pub fn from_argument(value: u32) -> Option<Self> {
match value {
0 => Some(Self::Add),
@@ -354,7 +422,9 @@ impl ArithmeticOp {
}
}
impl EndorNumber {
/// eval 一元负号:浮点翻符号位,其余走零减当前值,因此仍受运算预算限制。
pub fn negated(&self) -> Result<Self, VmError> {
// 翻转符号位保留负零和 NaN payload,不经算术规范化。
if let Some(EndorReal::BinaryFloat(bits)) = self.as_real() {
return Ok(Self::Real(EndorReal::BinaryFloat(bits ^ (1_u64 << 63))));
}
@@ -362,11 +432,14 @@ impl EndorNumber {
}
/// Explicit narrowing for graphics/FFI boundaries; exact values themselves
/// retain their representation. Non-real or non-finite values are rejected.
/// 宿主图形/FFI 显式收窄,拒绝复数、非有限值和不可转换范围。
pub fn to_f64(&self) -> Result<f64, VmError> {
use num_traits::ToPrimitive;
// 先要求 Real,不能静默丢弃复数虚部。
let real = self
.as_real()
.ok_or(VmError::UnsupportedOperation("complex to float"))?;
// 浮点还原原位,精确表示先转 BigRational 再收窄。
let value = if let EndorReal::BinaryFloat(bits) = real {
f64::from_bits(*bits)
} else {
@@ -374,13 +447,17 @@ impl EndorNumber {
"number exceeds binary float range",
))?
};
// 图形/FFI 边界不接受 NaN 或无穷。
if !value.is_finite() {
return Err(VmError::InvalidArgument("non-finite float conversion"));
}
Ok(value)
}
/// eval 二元算术:i64 安全快路径,任一浮点选择 float,否则精确有理数运算。
pub fn arithmetic(&self, other: &Self, op: ArithmeticOp) -> Result<Self, VmError> {
// 双方为可容纳 i64 的 Integer 才用快路径;运算溢出回退,不截断。
if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) {
// 加减乘检查溢出,除法仅整除且不溢出时保持整数快路径。
let integer = match op {
ArithmeticOp::Add => a.checked_add(b),
ArithmeticOp::Subtract => a.checked_sub(b),
@@ -390,11 +467,13 @@ impl EndorNumber {
.filter(|&r| r == 0)
.and_then(|_| a.checked_div(b)),
};
// 快路径成功后包装为规范化整数。
if let Some(result) = integer {
return Ok(Self::from(result));
}
}
use num_traits::{ToPrimitive, Zero};
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
let (a, b) = (
self.as_real()
.ok_or(VmError::UnsupportedOperation("complex arithmetic"))?,
@@ -402,8 +481,11 @@ impl EndorNumber {
.as_real()
.ok_or(VmError::UnsupportedOperation("complex arithmetic"))?,
);
// 显式浮点参与才启用 float,精确十进制本身不自动选 float。
if matches!(a, EndorReal::BinaryFloat(_)) || matches!(b, EndorReal::BinaryFloat(_)) {
// 混合算术中精确侧可收窄,浮点侧还原原位。
let convert = |n: &EndorReal| -> Result<f64, VmError> {
// 已为 float 则还原,否则精确转换后检查能否收窄。
if let EndorReal::BinaryFloat(bits) = n {
Ok(f64::from_bits(*bits))
} else {
@@ -412,44 +494,55 @@ impl EndorNumber {
))
}
};
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
let (a, b) = (convert(a)?, convert(b)?);
// 两种运算模式都拒绝零除,包括浮点正负零。
if matches!(op, ArithmeticOp::Divide) && b == 0. {
return Err(VmError::InvalidArgument("division by zero"));
}
// 执行指定运算;浮点后检查有限性,精确后检查结果预算。
let result = match op {
ArithmeticOp::Add => a + b,
ArithmeticOp::Subtract => a - b,
ArithmeticOp::Multiply => a * b,
ArithmeticOp::Divide => a / b,
};
// 拒绝 NaN 传播或溢出为无穷,不返回非有限算术结果。
if !result.is_finite() {
return Err(VmError::InvalidArgument("non-finite arithmetic result"));
}
return Ok(Self::binary_float(result));
}
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
let (a, b) = (exact_real(a)?, exact_real(b)?);
// 两种运算模式都拒绝零除,包括浮点正负零。
if matches!(op, ArithmeticOp::Divide) && b.is_zero() {
return Err(VmError::InvalidArgument("division by zero"));
}
// 执行指定运算;浮点后检查有限性,精确后检查结果预算。
let result = match op {
ArithmeticOp::Add => a + b,
ArithmeticOp::Subtract => a - b,
ArithmeticOp::Multiply => a * b,
ArithmeticOp::Divide => a / b,
};
// 限制运算后分子/分母位数,构造保存上限与实际运算预算不同。
if result.numer().bits() > 16_384 || result.denom().bits() > 16_384 {
return Err(VmError::InvalidArgument(
"arithmetic result exceeds operation budget",
));
}
// 可整除输出 Integer,否则输出约分后 Rational,不伪造 Decimal 小数位。
if result.is_integer() {
Self::integer(&result.to_integer().to_string())
} else {
Self::rational(&result.numer().to_string(), &result.denom().to_string())
}
}
/// eval/数值载荷数学比较,拒绝复数,处理 NaN 无序与无穷,不依赖 Rust Eq。
pub fn numeric_compare(&self, other: &Self, op: CompareOp) -> Result<bool, VmError> {
use std::cmp::Ordering;
// 先要求两个输入均为实数;后续按精确/浮点模式转换。
let (a, b) = (
self.as_real()
.ok_or(VmError::UnsupportedOperation("complex comparison"))?,
@@ -457,6 +550,7 @@ impl EndorNumber {
.as_real()
.ok_or(VmError::UnsupportedOperation("complex comparison"))?,
);
// 双方均为可容纳 i64 的 Integer 时直接比较,不需要大整数分配。
if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) {
return Ok(match op {
CompareOp::Eq => a == b,
@@ -469,15 +563,18 @@ impl EndorNumber {
}
// Finite binary floats compare as exact binary rationals, so a large
// integer never loses precision merely to answer an ordering question.
// 双浮点使用 partial_cmp;有限混合比较转精确分数,避免大整数失真。
let order = if let (EndorReal::BinaryFloat(a), EndorReal::BinaryFloat(b)) = (a, b) {
f64::from_bits(*a).partial_cmp(&f64::from_bits(*b))
} else {
// 识别混合比较中的浮点 NaN/无穷,其他表示只有有限值。
let nonfinite = |n: &EndorReal| match n {
EndorReal::BinaryFloat(bits) if !f64::from_bits(*bits).is_finite() => {
Some(f64::from_bits(*bits))
}
_ => None,
};
// NaN 无序,无穷按符号超出有限值,其他输入走精确比较。
match (nonfinite(a), nonfinite(b)) {
(Some(a), _) if a.is_nan() => None,
(_, Some(b)) if b.is_nan() => None,
@@ -494,6 +591,7 @@ impl EndorNumber {
_ => Some(exact_real(a)?.cmp(&exact_real(b)?)),
}
};
// 无序令 Eq 和有序关系为 false,Ne 为 true。
Ok(match op {
CompareOp::Eq => order == Some(Ordering::Equal),
CompareOp::Ne => order != Some(Ordering::Equal),
@@ -504,10 +602,13 @@ impl EndorNumber {
})
}
}
/// 算术/比较/收窄共用精确有理数转换,有限浮点按精确二进制分数处理,有操作预算。
fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
use num_bigint::BigInt;
use num_rational::BigRational;
// 大整数转换先限制文本长度,含可能的符号。
let integer = |s: &str| -> Result<BigInt, VmError> {
// 超预算报错,不以截断或浮点近似继续。
if s.len() > 4096 {
return Err(VmError::InvalidArgument(
"numeric operand exceeds operation budget",
@@ -525,7 +626,9 @@ fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
EndorReal::BinaryFloat(bits) => BigRational::from_float(f64::from_bits(*bits))
.ok_or(VmError::InvalidArgument("non-finite exact conversion")),
EndorReal::Decimal(n) => {
// 十进制已通过构造语法检查,这里为运算解析指数。
let parts: Vec<_> = n.literal.split(['e', 'E']).collect();
// 保存允许很长指数,实际运算要求可转为 i32。
let exponent = if parts.len() == 2 {
parts[1].parse::<i32>().map_err(|_| {
VmError::InvalidArgument("numeric exponent exceeds operation budget")
@@ -533,15 +636,21 @@ fn exact_real(value: &EndorReal) -> Result<num_rational::BigRational, VmError> {
} else {
0
};
// 尾数的小数位数须从指数扣除,确定整体十进制缩放。
let decimals = parts[0].split_once('.').map_or(0, |(_, s)| s.len()) as i64;
// 提升到 i64 计算指数差,随后检查绝对幂次预算。
let power = i64::from(exponent) - decimals;
// 限制生成巨大十次幂的工作量,不只限制结果显示长度。
if power.abs() > 4096 {
return Err(VmError::InvalidArgument(
"numeric exponent exceeds operation budget",
));
}
// 去掉小数点但保留符号,将尾数转为整数。
let digits = integer(&parts[0].replace('.', ""))?;
// 预算通过才计算精确的 10 次幂,不调用浮点 pow。
let factor = BigInt::from(10).pow(power.unsigned_abs() as u32);
// 负幂放分母,非负幂乘分子;BigRational 规范化结果。
Ok(if power < 0 {
BigRational::new(digits, factor)
} else {