From c25cb6756ef94f29a3d5b2b8e9010d6dd56c86ba Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:30:28 +0800 Subject: [PATCH] docs(objects): explain numeric representations and operation budgets --- crates/objects/src/number.rs | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 1e9449f..3d2bd80 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -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 { + // 拆符号后校验数字,空串或只有符号均非法。 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 { + // 先读 u64 幅值以容纳 i64::MIN 的绝对值,再检查有符号范围。 let magnitude = self.digits.parse::().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 { + // 校验时去符号,但最终仍保存完整 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 { + // 构造时拒绝零分母,后续零判断只需看分子。 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 { Ok(Self::Real(EndorReal::Integer(EndorInteger::parse(text)?))) } + /// 编译器/宿主构造保留字面量的十进制实数。 pub fn decimal(text: &str) -> Result { Ok(Self::Real(EndorReal::Decimal(EndorDecimal::parse(text)?))) } + /// 宿主/运算结果从两段整数文本建立有理数,拒绝零分母。 pub fn rational(numerator: &str, denominator: &str) -> Result { 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 { 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 for EndorNumber { + /// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。 fn from(value: i64) -> Self { Self::integer(&value.to_string()).expect("i64 always has valid decimal digits") } } impl From 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 { Ok(self.value.to_string()) } + /// Store 真值协议按非零判断,复数要求两分量都为零才为假。 fn is_true(&self) -> Result { Ok(!self.value.is_zero()) } + /// 统一数值哈希尚未定义,拒绝以免数学相等而表示不同的键哈希不兼容。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("numeric hash")) } + /// Store 对象比较协议,只在对方也是数值载荷时处理,否则返回 None。 fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result, VmError> { + // 异类载荷不强行比较,交给 Store 的反向协议或身份回退。 if let Some(other) = other.as_any().downcast_ref::() { + // 复数目前连相等比较也不支持,明确报协议错误。 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 { match value { 0 => Some(Self::Add), @@ -354,7 +422,9 @@ impl ArithmeticOp { } } impl EndorNumber { + /// eval 一元负号:浮点翻符号位,其余走零减当前值,因此仍受运算预算限制。 pub fn negated(&self) -> Result { + // 翻转符号位保留负零和 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 { 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 { + // 双方为可容纳 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 { + // 已为 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 { 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 { use num_bigint::BigInt; use num_rational::BigRational; + // 大整数转换先限制文本长度,含可能的符号。 let integer = |s: &str| -> Result { + // 超预算报错,不以截断或浮点近似继续。 if s.len() > 4096 { return Err(VmError::InvalidArgument( "numeric operand exceeds operation budget", @@ -525,7 +626,9 @@ fn exact_real(value: &EndorReal) -> Result { 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::().map_err(|_| { VmError::InvalidArgument("numeric exponent exceeds operation budget") @@ -533,15 +636,21 @@ fn exact_real(value: &EndorReal) -> Result { } 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 {