diff --git a/crates/oxide_builtins/src/number.rs b/crates/oxide_builtins/src/number.rs index 205b4c9b..b3ad0605 100644 --- a/crates/oxide_builtins/src/number.rs +++ b/crates/oxide_builtins/src/number.rs @@ -346,6 +346,18 @@ fn this_number_value(vm: &mut H, this_val: JsValue) -> Result f64 { + if n.is_nan() || n == 0.0 { + 0.0 + } else if n.is_infinite() { + n + } else { + n.trunc() + } +} + /// f/p/radix 参数转换:完整 ToNumber(对象执行 ToPrimitive 且 valueOf/toString /// 抛出的异常原样传播)后走 ToIntegerOrInfinity。纯核 `to_integer_or_infinity` /// 不传播异常,不得用于参数位。 @@ -359,13 +371,281 @@ fn coerce_to_integer_or_infinity(vm: &mut H, arg: JsValue) -> Result< return Err(crate::error::create_type_error(vm, "Cannot convert argument to a number")); } }; - if raw.is_nan() || raw == 0.0 { - Ok(0.0) - } else if raw.is_infinite() { - Ok(raw) - } else { - Ok(raw.trunc()) + Ok(to_integer_or_infinity(raw)) +} + +// ── 精确十进制展开与 half-up 舍入 ── +// toFixed/toExponential/toPrecision 共享:f64 是二进有理数,精确十进制展开 +// 有限;在 digit 串上按 keep 位 half-up 舍入(tie 取较大者,规范语义)并 +// 传播进位。Rust `format!("{:.N$}")` 是 half-even,tie 面与规范分歧, +// 仅 String() 路径可用。 + +/// base 2^32 小整数(limb 升序),承载 f64 展开所需位宽(≤ 2^1127)。 +#[derive(Clone)] +struct Dec(Vec); + +impl Dec { + fn from_u64(v: u64) -> Self { + Dec(vec![v as u32, (v >> 32) as u32]) } + + fn trim(&mut self) { + while self.0.len() > 1 && *self.0.last().unwrap() == 0 { + self.0.pop(); + } + } + + fn is_zero(&self) -> bool { + self.0.iter().all(|&l| l == 0) + } + + /// 左移 k 位(× 2^k):原位高→低;word j 的低段来自 limb j−ws, + /// 高段来自 limb j−ws−1(处理 j 时该 limb 尚未被覆写)。 + fn shl(&mut self, k: u32) { + let ws = (k / 32) as usize; + let bs = k % 32; + let n = self.0.len(); + let orig = self.0[..n].to_vec(); + self.0 = vec![0; n + ws + 1]; + let mut top = 0u32; + for i in (0..n).rev() { + let v = orig[i]; + if i == n - 1 && bs != 0 { + top = v >> (32 - bs); + } + let low = if bs == 0 { v } else { v << bs }; + let carry_in = if i == 0 || bs == 0 { 0 } else { orig[i - 1] >> (32 - bs) }; + self.0[i + ws] = low | carry_in; + } + self.0[n + ws] = top; + self.trim(); + } + + /// 右移 q 位(截断),q 超位宽时归 0。 + fn shr(&mut self, q: u32) { + let ws = (q / 32) as usize; + if ws >= self.0.len() { + self.0 = vec![0]; + return; + } + self.0.drain(..ws); + let bs = q % 32; + if bs != 0 { + let mut borrow = 0u32; + for limb in self.0.iter_mut() { + let cur = *limb; + *limb = (cur >> bs) | borrow; + borrow = cur << (32 - bs); + } + } + self.trim(); + } + + /// 保留低 q 位(& (2^q − 1))。 + fn rem_pow2(&mut self, q: u32) { + let total = (self.0.len() * 32) as u32; + if q >= total { + return; + } + let wm = (q / 32) as usize; + let bm = q % 32; + if bm != 0 { + self.0[wm] &= (1u32 << bm) - 1; + } + self.0.truncate(wm + 1); + self.trim(); + } + + /// 乘单个小因子(digit 步进用 ×10)。 + fn mul_small(&mut self, k: u32) { + let mut carry = 0u64; + for limb in self.0.iter_mut() { + let acc = (*limb as u64) * k as u64 + carry; + *limb = acc as u32; + carry = acc >> 32; + } + if carry != 0 { + self.0.push(carry as u32); + } + } + + /// 就地整除 10,返回余数(一个十进制 digit)。 + fn divmod10(&mut self) -> u8 { + let mut rem = 0u64; + for i in (0..self.0.len()).rev() { + let cur = (rem << 32) | self.0[i] as u64; + self.0[i] = (cur / 10) as u32; + rem = cur % 10; + } + self.trim(); + rem as u8 + } +} + +/// f64 精确十进制展开:前 count 个有效数字与首位十进制指数。 +/// +/// 返回 (digits, e):x = 0.d1d2… × 10^(e+1),d1 ≠ 0;展开恰在 count 前 +/// 终止时位数不足,缺失位恒 0(调用方按 0 补齐)。 +fn significant_digits(x: f64, count: usize) -> (Vec, i32) { + debug_assert!(x > 0.0 && x.is_finite() && count > 0); + let b = x.to_bits(); + let (m, p) = if (b >> 52) == 0 { + // 次正规:值 = frac × 2^-1074(隐含位 0)。 + (b as u64, -1074) + } else { + // 正规:显式隐含位补齐尾数(指数位先掩掉,勿随位模式带入)。 + (((b as u64 & ((1u64 << 52) - 1)) | (1u64 << 52)), ((b >> 52) as i32) - 1075) + }; + if p >= 0 { + // 整数面:V = M × 2^p,位数 D 定首位指数,弃低位后逐位提取。 + let mut v = Dec::from_u64(m); + v.shl(p as u32); + let mut d = 0usize; + let mut tmp = v.clone(); + while !tmp.is_zero() { + tmp.divmod10(); + d += 1; + } + let e = d as i32 - 1; + let take = count.min(d); + for _ in 0..(d - take) { + v.divmod10(); + } + let mut digits = Vec::with_capacity(take); + for _ in 0..take { + digits.push(v.divmod10()); + } + digits.reverse(); + (digits, e) + } else { + // 分数面:整数部分 + 小数余数逐步 ×10 取商(前导零定 e)。 + let q = (-p) as u32; + let (int_part, mut r) = if q < 64 { + (m >> q, Dec::from_u64(m & ((1u64 << q) - 1))) + } else { + (0u64, Dec::from_u64(m)) + }; + let mut digits: Vec = Vec::new(); + let mut e: Option = None; + if int_part != 0 { + let mut ip = int_part; + let mut id = Vec::new(); + while ip > 0 { + id.push((ip % 10) as u8); + ip /= 10; + } + id.reverse(); + e = Some(id.len() as i32 - 1); + digits.extend(id); + } + let mut pos = 0usize; + while digits.len() < count && !r.is_zero() { + r.mul_small(10); + let mut t = r.clone(); + t.shr(q); + let d = t.0.first().copied().unwrap_or(0) as u8; + r.rem_pow2(q); + pos += 1; + // 无前导整数时,首个非零位之前的零不显著(定 e); + // 有整数部分后每一位(含零)均显著。 + if d == 0 && e.is_none() { + continue; + } + if e.is_none() { + e = Some(-(pos as i32)); + } + digits.push(d); + } + (digits, e.unwrap_or(0)) + } +} + +/// 精确 digit 串 half-up 舍入:rd 为舍入判定位下标(负下标按 0), +/// 进位自最后一个渲染位 rd−1 向首位传播;渲染位之后的输入位原样保留 +/// (不再被读取)。 +/// +/// 判定位 ≥ 5 进位(规范 tie 取较大 n:恰好相等也取较大者);全链进位时 +/// 值恰为 10^(e+1),低位全 0、e + 1(9.99→10.0 型)。位数不足时缺失位 +/// 恒 0(展开已终止),不进位。 +fn round_digits(d: &[u8], e: i32, rd: i32) -> (Vec, i32) { + // 舍入位在展开之前(rd < 0)时无渲染位,输出空串。 + let mut out = if rd < 0 { Vec::new() } else { d.to_vec() }; + let rd_digit = if rd >= 0 { d.get(rd as usize).copied().unwrap_or(0) } else { 0 }; + if rd_digit < 5 { + return (out, e); + } + let mut k = rd - 1; + loop { + if k < 0 { + // 无渲染位可进:0.x 型直接 10^e'。 + return (vec![1], e + 1); + } + out[k as usize] += 1; + if out[k as usize] < 10 { + return (out, e); + } + out[k as usize] = 0; + if k == 0 { + // 全链进位:值恰为 10^(e+1),低位全 0。 + return (vec![1], e + 1); + } + k -= 1; + } +} + +/// digit i 落十进制位置 e−i;越界(含负下标)按 0。 +fn dig(d: &[u8], i: i32) -> u8 { + if i >= 0 { + d.get(i as usize).copied().unwrap_or(0) + } else { + 0 + } +} + +/// 定点形渲染:整数部分自位置 e 至 0,小数部分 f 位,越界位按 0。 +fn fixed_string(d: &[u8], e: i32, f: usize, neg: bool) -> String { + let mut s = String::new(); + if neg { + s.push('-'); + } + if e < 0 { + s.push('0'); + } else { + // 整数部分自高位到低位:digit i 落位置 e−i。 + for i in 0..=e { + s.push((b'0' + dig(d, i)) as char); + } + } + if f > 0 { + s.push('.'); + for p in 1..=f as i32 { + s.push((b'0' + dig(d, e + p)) as char); + } + } + s +} + +/// 科学式渲染:d1[.d2…d(f+1)] 恒带符号指数。 +fn exp_string(d: &[u8], e: i32, f: usize, neg: bool) -> String { + let mut s = String::new(); + if neg { + s.push('-'); + } + s.push((b'0' + dig(d, 0)) as char); + if f > 0 { + s.push('.'); + for i in 1..=f as i32 { + s.push((b'0' + dig(d, i)) as char); + } + } + s.push('e'); + if e < 0 { + s.push('-'); + } else { + s.push('+'); + } + s.push_str(&e.abs().to_string()); + s } /// `Number.prototype.toLocaleString()`:locale 参数忽略,恒按十进制输出。 @@ -407,13 +687,7 @@ pub fn number_to_string(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert radix to a number")); } }; - let r = if raw.is_nan() || raw == 0.0 { - 0.0 - } else if raw.is_infinite() { - raw - } else { - raw.trunc() - }; + let r = to_integer_or_infinity(raw); if !(2.0..=36.0).contains(&r) { return NativeResult::Err(crate::error::create_range_error( vm, @@ -467,7 +741,8 @@ pub fn number_to_string(vm: &mut H, args: &[u8]) -> NativeResult { /// /// 步序:先 thisNumberValue,再转换 fractionDigits 并做范围校验(范围检查 /// 先于 NaN 短路,NaN.toFixed(Infinity) 须抛 RangeError);随后 x ≥ 10^21 -/// 退化为 String(x) 科学式(±Infinity 同形,经此分支输出专名)。 +/// 退化为 String(x) 科学式(±Infinity 同形,经此分支输出专名);其余按精确 +/// digit 串 half-up 舍入渲染(tie 取较大者,Rust 格式化 half-even 不可用)。 pub fn number_to_fixed(vm: &mut H, args: &[u8]) -> NativeResult { let n = match this_number_value(vm, vm.reg(args[0])) { Ok(v) => v, @@ -499,9 +774,19 @@ pub fn number_to_fixed(vm: &mut H, args: &[u8]) -> NativeResult { }; return NativeResult::Ok(vm.new_string_owned(s)); } - let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n }; - let formatted = format!("{:.precision$}", n_abs, precision = raw as usize); - NativeResult::Ok(vm.new_string(&formatted)) + let f = raw as usize; + let neg = n < 0.0; + if n == 0.0 { + // -0 落正分支:定点形无符号位可携。 + return NativeResult::Ok(vm.new_string_owned(fixed_string(&[], 0, f, false))); + } + // 定点形覆盖位置 e..−f;进位传播最多使 e+1,覆盖到舍入位共 e+f+2 位。 + // 舍入判定位 = 位置 −(f+1) 的 digit(e+f+1,负下标按 0)。 + let (_, e0) = significant_digits(n.abs(), 1); + let count = (e0 + f as i32 + 2).max(1) as usize; + let (d, e) = significant_digits(n.abs(), count); + let (d, e) = round_digits(&d, e, e0 + f as i32 + 1); + NativeResult::Ok(vm.new_string_owned(fixed_string(&d, e, f, neg))) } /// `Number.isInteger`:参数是有限且无小数部分的数值才返回 true。 @@ -530,7 +815,8 @@ pub fn number_is_safe_integer(vm: &mut H, args: &[u8]) -> NativeResul /// /// 步序:先 thisNumberValue;precision 缺省直接返 ToString(x)(含 NaN/±∞ /// 全形);再转 precision,NaN/±∞ 专名先于范围校验((±∞).toPrecision(1000) -/// 返 "Infinity" 而非 RangeError),最后舍入渲染(指数恒带符号)。 +/// 返 "Infinity" 而非 RangeError);最后按精确 digit 串 half-up 舍入, +/// 定点/指数分界用**进位后**的首位指数(999.toPrecision(2) = "1.0e+3")。 pub fn number_to_precision(vm: &mut H, args: &[u8]) -> NativeResult { let n = match this_number_value(vm, vm.reg(args[0])) { Ok(v) => v, @@ -556,34 +842,34 @@ pub fn number_to_precision(vm: &mut H, args: &[u8]) -> NativeResult { )); } let precision = raw as usize; - let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n.abs() }; - let is_neg = n.is_sign_negative() && !(n == 0.0); - let e = if n_abs == 0.0 { 0i32 } else { n_abs.log10().floor() as i32 }; - let formatted = if e >= -6 && e < precision as i32 { - let dec = ((precision as i32 - e - 1).max(0)) as usize; - format!("{:.dec$}", n_abs, dec = dec) - } else { - // Rust 的 {:e} 指数不带符号,规范要求恒带 +/-。 - let mut s = format!("{:.dec$e}", n_abs, dec = precision - 1); - if let Some(e_pos) = s.find('e') { - if !s[e_pos + 1..].starts_with('-') { - s.insert(e_pos + 1, '+'); + let neg = n < 0.0; + if n == 0.0 { + // -0 落正分支;"0" 后跟 precision−1 个零。 + let mut s = String::from("0"); + if precision > 1 { + s.push('.'); + for _ in 0..precision - 1 { + s.push('0'); } } - s - }; - if is_neg { - NativeResult::Ok(vm.new_string(&format!("-{}", formatted))) - } else { - NativeResult::Ok(vm.new_string(&formatted)) + return NativeResult::Ok(vm.new_string_owned(s)); } + let (d, e) = significant_digits(n.abs(), precision + 1); + let (d, e) = round_digits(&d, e, precision as i32); + let s = if e >= -6 && e < precision as i32 { + fixed_string(&d, e, (precision as i32 - e - 1) as usize, neg) + } else { + exp_string(&d, e, precision - 1, neg) + }; + NativeResult::Ok(vm.new_string_owned(s)) } /// `Number.prototype.toExponential(digits)`:按科学计数法输出; /// digits 缺省/undefined 取最短精确有效数字数,超范围抛 RangeError。 /// /// 步序:先 thisNumberValue,再转换 fractionDigits(对象参数执行 ToPrimitive -/// 且异常原样传播);NaN/±∞ 专名先于范围检查;指数恒带符号。 +/// 且异常原样传播);NaN/±∞ 专名先于范围检查;缺省位数走最短 round-trip +/// 前缀,显式位数按精确 digit 串 half-up 舍入(tie 取较大者);指数恒带符号。 pub fn number_to_exponential(vm: &mut H, args: &[u8]) -> NativeResult { let n = match this_number_value(vm, vm.reg(args[0])) { Ok(v) => v, @@ -610,37 +896,28 @@ pub fn number_to_exponential(vm: &mut H, args: &[u8]) -> NativeResult "toExponential() fractionDigits must be between 0 and 100", )); } - let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n.abs() }; - let sign_prefix = if n < 0.0 { "-" } else { "" }; + let neg = n < 0.0; + if n == 0.0 { + // -0 落正分支:科学式 "0e+0" 形。 + let f = if has_digits { raw as usize } else { 0 }; + return NativeResult::Ok(vm.new_string_owned(exp_string(&[], 0, f, false))); + } if !has_digits { - let formatted = format!("{:e}", n_abs); - let mut s = formatted; + // 缺省位数 = Rust {:e} 最短 round-trip 形(规范"最短精确有效数字数" + // 同口径),仅补指数符号。 + let mut s = format!("{:e}", n.abs()); if let Some(e_pos) = s.find('e') { - let mut mantissa = s[..e_pos].to_string(); - while mantissa.ends_with('0') && mantissa.len() > 1 { - mantissa.pop(); - } - mantissa = mantissa.trim_end_matches('.').to_string(); - if mantissa.contains('.') { - s = format!("{}{}", mantissa, &s[e_pos..]); - } - // 指数恒带符号(Rust 正指数不输出 +)。 if !s[e_pos + 1..].starts_with('-') { s.insert(e_pos + 1, '+'); } } - let result = format!("{}{}", sign_prefix, s); + let result = format!("{}{}", if neg { "-" } else { "" }, s); return NativeResult::Ok(vm.new_string_owned(result)); } - let formatted = format!("{:.digits$e}", n_abs, digits = raw as usize); - // 指数恒带符号(Rust 正指数不输出 +)。 - let mut s = formatted; - if let Some(e_pos) = s.find('e') { - if !s[e_pos + 1..].starts_with('-') { - s.insert(e_pos + 1, '+'); - } - } - NativeResult::Ok(vm.new_string(&format!("{}{}", sign_prefix, s))) + let f = raw as usize; + let (d, e) = significant_digits(n.abs(), f + 2); + let (d, e) = round_digits(&d, e, f as i32 + 1); + NativeResult::Ok(vm.new_string_owned(exp_string(&d, e, f, neg))) } /// `Number.prototype.valueOf`:返回包装对象的原始 number; diff --git a/crates/oxide_vm/tests/builtins_number_tests.rs b/crates/oxide_vm/tests/builtins_number_tests.rs index a9854988..c8769eb2 100644 --- a/crates/oxide_vm/tests/builtins_number_tests.rs +++ b/crates/oxide_vm/tests/builtins_number_tests.rs @@ -447,3 +447,106 @@ fn number_proto_tag_survives_dirty_rebuild() { assert_eq!(str_of(&mut vm, "Number.prototype.toFixed(1)"), "0.0"); assert_eq!(str_of(&mut vm, "String(Number.parseInt === parseInt)"), "true"); } + +// 全表值对照 node v20.19.2 逐位核定(half-up 对 half-even 的分歧点、 +// 进位传播、位数增长、精确展开面、零与 -0 三面)。 +#[test] +fn number_format_tie_half_up_and_carry() { + let mut vm = Vm::new(); + let cases = [ + // tie 取较大者(away-from-zero),负数同形。 + ("(2.5).toExponential(0)", "3e+0"), + ("(2.4).toExponential(0)", "2e+0"), + ("(-2.5).toExponential(0)", "-3e+0"), + ("(-0.5).toExponential(0)", "-5e-1"), + ("(0.5).toFixed(0)", "1"), + ("(0.4).toFixed(0)", "0"), + ("(2.5).toFixed(0)", "3"), + ("(-0.5).toFixed(0)", "-1"), + ("(2.675).toFixed(2)", "2.67"), + // 精确展开低于 tie 点时不得进位(仅看最短形会误进位)。 + ("(1.15).toFixed(1)", "1.1"), + ("(1.005).toFixed(2)", "1.00"), + ("(1.005).toFixed(1)", "1.0"), + ("(0.05).toFixed(1)", "0.1"), + ("(0.145).toFixed(2)", "0.14"), + ("(1.2345).toFixed(3)", "1.234"), + ("(1.0005).toFixed(3)", "1.000"), + ("(0.615).toFixed(2)", "0.61"), + // 进位传播与位数增长(9.99→10.0 型)。 + ("(9.999).toFixed(1)", "10.0"), + ("(9.999).toFixed(0)", "10"), + ("(-9.999).toFixed(1)", "-10.0"), + ("(0.9999).toExponential(0)", "1e+0"), + ("(-0.9999).toExponential(0)", "-1e+0"), + ("(9.9999999999999999).toFixed(0)", "10"), + ("(999).toPrecision(2)", "1.0e+3"), + ("(-999).toPrecision(2)", "-1.0e+3"), + ("(99.99).toPrecision(3)", "100"), + ("(999999).toPrecision(2)", "1.0e+6"), + ("(25).toExponential(0)", "3e+1"), + ("(12345).toExponential(3)", "1.235e+4"), + // 精确整数展开(位数超最短形)。 + ("(1000000000000000128).toFixed(0)", "1000000000000000128"), + ("(9007199254740993).toFixed(0)", "9007199254740992"), + ("(9.9e20).toFixed(0)", "990000000000000000000"), + // 进位后 e 决定定点/指数分界。 + ("(1.23456).toPrecision(4)", "1.235"), + ("(123456).toPrecision(3)", "1.23e+5"), + ("(1e-6).toPrecision(2)", "0.0000010"), + ("(1e-7).toPrecision(2)", "1.0e-7"), + ("(0.5).toPrecision(1)", "0.5"), + ("(0.05).toPrecision(2)", "0.050"), + ("(10).toPrecision(1)", "1e+1"), + // 零与 -0 三面(-0 落正分支)。 + ("(0).toFixed(3)", "0.000"), + ("(0).toExponential(2)", "0.00e+0"), + ("(0).toPrecision(2)", "0.0"), + ("(-0).toFixed(3)", "0.000"), + ("(-0).toExponential(2)", "0.00e+0"), + ("(-0).toPrecision(2)", "0.0"), + // x ≥ 1e21 走科学式 String(f64 已达 1e21)。 + ("(1e21-1).toFixed(0)", "1e+21"), + ("(-1e21).toFixed(0)", "-1e+21"), + ("(1234567890123456789012345678901234567890).toFixed(0)", "1.2345678901234568e+39"), + // 长小数面(展开终止后按 0 补齐)。 + ("(3.141592653589793).toPrecision(17)", "3.1415926535897931"), + ( + "(3.141592653589793).toFixed(100)", + "3.1415926535897931159979634685441851615905761718750000000000000000000000000000000000000000000000000000", + ), + // 次正规数与超大指数面。 + ("(1e308).toPrecision(2)", "1.0e+308"), + ("(1.5e308).toExponential(1)", "1.5e+308"), + ("(1e-323).toExponential(2)", "9.88e-324"), + ("(5e-324).toExponential(0)", "5e-324"), + ("(4.9e-324).toFixed(1)", "0.0"), + ]; + for (src, expected) in cases { + assert_eq!(str_of(&mut vm, src), expected, "for {}", src); + } +} + +#[test] +fn number_format_default_shortest_digits() { + let mut vm = Vm::new(); + // 缺省位数 = 最短精确有效数字数 − 1(round-trip 前缀,非任意最短十进制形)。 + let cases = [ + ("(123.456).toExponential()", "1.23456e+2"), + ("(2).toExponential()", "2e+0"), + ("(0.1).toExponential()", "1e-1"), + ("(0.3).toExponential()", "3e-1"), + ("(1e-7).toExponential()", "1e-7"), + ("(1.1e-32).toExponential()", "1.1e-32"), + ("(1e20).toExponential()", "1e+20"), + ("(1e-20).toExponential()", "1e-20"), + ("(0.1+0.2).toExponential()", "3.0000000000000004e-1"), + ("(123456789012345678901234567890.5).toExponential()", "1.2345678901234568e+29"), + ("(42).toPrecision()", "42"), + ("(0.0000001).toPrecision(3)", "1.00e-7"), + ("(0.00000012345).toPrecision(2)", "1.2e-7"), + ]; + for (src, expected) in cases { + assert_eq!(str_of(&mut vm, src), expected, "for {}", src); + } +}