Files
OxideJS-New/crates/oxide_builtins/src/number.rs
T

960 lines
36 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, ProtoKind, ToPrimitiveHint, VmHost};
/// 整值且 i32 域内的有限数转 int 表示;-0 排除
/// (int 表示丢符号,规范要求 `Number(-0)` 得 -0)。
fn i32_of_integral(n: f64) -> Option<i32> {
if n == 0.0 && n.is_sign_negative() {
return None;
}
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
Some(n as i32)
} else {
None
}
}
/// JS `Number()` 构造逻辑:把参数按 ToNumber 语义转换。
/// 普通调用返回原始 number(整数走 int 表示);new 语义返回 `[[NumberData]]` 包装对象。
pub fn number_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = if args.len() > 1 {
let raw = vm.reg(args[1]);
// ToPrimitive 先解盒:BigInt 原始值走 lossy 转换(BigIntToNumber 不抛错,
// 显式 `Number(bigint)` 是规范唯一合法入口);其余经 bounded ToNumber
// 入口(Symbol 抛 TypeError)。
let prim = match vm.coerce_primitive_bounded(raw, ToPrimitiveHint::Number) {
Ok(p) => p,
Err(_) => {
// 对象经 ToPrimitive 转换时 toString/valueOf 可抛异常,须原样传播原始异常。
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a number"));
}
};
if prim.is_bigint() {
oxide_runtime_api::bigint_to_f64(unsafe { oxide_runtime_api::bigint_data(prim) })
} else if prim.is_symbol() {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a number"));
} else {
oxide_runtime_api::to_number(prim)
}
} else {
0.0
};
let number_proto = vm.builtin_proto(ProtoKind::NumberProto);
let is_ctor = if let Some(this_reg) = args.first().copied() {
let this_val = vm.reg(this_reg);
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
false
} else {
let proto_ptr = unsafe { (*ptr).proto().as_js_object_ptr() };
!proto_ptr.is_null() && std::ptr::eq(proto_ptr, number_proto)
}
} else {
false
}
} else {
false
};
if is_ctor {
let this_val = vm.reg(args[0]);
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
obj.type_tag = oxide_types::object::JsObject::OBJ_TYPE_NUMBER_OBJ;
let boxed = i32_of_integral(n).map(JsValue::int).unwrap_or_else(|| JsValue::float(n));
obj.set_boxed_value(boxed);
return NativeResult::Ok(this_val);
}
NativeResult::Ok(i32_of_integral(n).map(JsValue::int).unwrap_or_else(|| JsValue::float(n)))
}
/// `Number.isNaN`:参数严格等于 NaN 才返回 true(不做隐式类型转换)。
///
/// 步 1 为严格判型:非 Number 原始类型(含 Number 对象)直接 false,
/// 不走 ToNumber 强转(否则 "NaN"/对象装箱会误报 true)。
pub fn number_is_nan<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let val = vm.reg(args[1]);
if !val.is_int() && !val.is_double() {
return NativeResult::Ok(JsValue::bool(false));
}
NativeResult::Ok(JsValue::bool(oxide_runtime_api::to_number(val).is_nan()))
}
/// `Number.isFinite`:参数为有限数才返回 true(不做隐式类型转换)。
///
/// 步 1 为严格判型:非 Number 原始类型(含 Number 对象)直接 false,
/// 不走 ToNumber 强转(否则 "1" 等会误报 true)。
pub fn number_is_finite<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let val = vm.reg(args[1]);
if !val.is_int() && !val.is_double() {
return NativeResult::Ok(JsValue::bool(false));
}
NativeResult::Ok(JsValue::bool(oxide_runtime_api::to_number(val).is_finite()))
}
/// 判定 parseInt/parseFloat 修剪时要剥掉的空白字符(规范 WhiteSpace 与
/// LineTerminator 集合的并集)。
///
/// 与 Rust `char::is_whitespace` 的差异:规范集合不含 U+0085(NEL),手工
/// 按白名单匹配避免误剥。
pub(crate) fn is_js_ws(c: char) -> bool {
matches!(
c,
'\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{1680}' | '\u{2000}'
..='\u{200A}'
| '\u{202F}'
| '\u{205F}'
| '\u{3000}'
| '\u{FEFF}'
| '\u{000A}'
| '\u{000D}'
| '\u{2028}'
| '\u{2029}'
)
}
/// ToInt32:把 f64 转成 mod 2^32 回绕的有符号 i32。
///
/// 输入值 NaN、±0、±∞ 一律得 0;其余先向零截断,再对 2^32 取欧几里得
/// 余(余数恒非负),故超大输入(如 2^40)按回绕而非饱和处理。
fn to_int32(n: f64) -> i32 {
if n.is_nan() || n.is_infinite() || n == 0.0 {
return 0;
}
(n.trunc().rem_euclid(4294967296.0) as u32) as i32
}
/// `parseInt(string, radix)`:按指定进制解析字符串,取最长有效数字前缀。
///
/// 先用规范空白白名单修剪首尾,再读首个 `+`/`-` 符号。radix 先经 ToInt32
/// 转换(mod 2^32 回绕),NaN 与 undefined 均得 0;转换后 R 非零且越出
/// [2,36] 时整体返回 NaN。仅当转换后 R 为 0 或 16 时剥除 `0x`/`0X` 前缀
/// 并改按十六进制解析;随后按确定的进制收集最长连续有效数字前缀并转为
/// f64(十进制走正确舍入,其余进制数学累加,均允许超 2^53 的舍入),无
/// 任何有效数字时返回 NaN;结果落在 i32 域内用 int 表示,`-0` 保留负零。
pub fn number_parse_int<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::float(f64::NAN));
}
// 参数按 ToString 完整转换:对象经 ToPrimitive(string hint),Symbol 抛
// TypeError;对象方法抛出的原生异常原样传播。
let s = match oxide_runtime_api::to_string_full(vm.reg(args[1]), vm) {
Ok(s) => s,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
};
let s = s.trim_start_matches(is_js_ws).trim_end_matches(is_js_ws);
// radix 经 ToInt32:缺省参数视为 undefined(ToInt32 → 0)。
let raw = if args.len() > 2 {
match vm.coerce_number_bounded(vm.reg(args[2])) {
Ok(n) => n,
Err(_) => {
// coercion 触发用户 valueOf/toString 抛出的异常经
// last_uncaught_value 恢复后原样重新抛出。
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert radix to a number"));
}
}
} else {
f64::NAN
};
let r = to_int32(raw);
// 读符号并跳过;R≠0 且越出 [2,36] 直接 NaN(保留 0 参与后续 0x 判定)。
let (neg, rest) = match s.as_bytes().first() {
Some(b'-') => (true, &s[1..]),
Some(b'+') => (false, &s[1..]),
_ => (false, s),
};
let radix_default: u32 = if r == 0 {
10
} else if (2..=36).contains(&r) {
r as u32
} else {
return NativeResult::Ok(JsValue::float(f64::NAN));
};
// 0x/0X 前缀:仅当原 R 为 0(缺省/undefined/NaN/0)或 16 时剥前缀转十六进制。
let (digits, radix) = if (r == 0 || r == 16)
&& rest.len() >= 2
&& rest.as_bytes()[0] == b'0'
&& (rest.as_bytes()[1] == b'x' || rest.as_bytes()[1] == b'X')
{
(&rest[2..], 16u32)
} else {
(rest, radix_default)
};
// 按进制收集最长连续有效数字前缀;十进制交 Rust 正确舍入解析
// (逐位 f64 累加对 20+ 位数字会产生 1 ulp 级偏差),其余进制
// 数学累加(2 的幂进制在 53 位内精确,超出按规范允许近似舍入)。
let mut end = 0usize;
for (i, c) in digits.char_indices() {
if c.to_digit(radix).is_none() {
break;
}
end = i + c.len_utf8();
}
if end == 0 {
return NativeResult::Ok(JsValue::float(f64::NAN));
}
let prefix = &digits[..end];
let acc = if radix == 10 {
match prefix.parse::<f64>() {
Ok(v) => v,
Err(_) => return NativeResult::Ok(JsValue::float(f64::NAN)),
}
} else {
let mut acc = 0.0f64;
for c in prefix.chars() {
acc = acc * radix as f64 + c.to_digit(radix).unwrap_or(0) as f64;
}
acc
};
let acc = if neg { -acc } else { acc };
if acc == 0.0 {
// 负零保留符号(parseInt("-0") → -0)。
return NativeResult::Ok(JsValue::float(if neg { -0.0 } else { 0.0 }));
}
if acc.fract() == 0.0 && acc >= i32::MIN as f64 && acc <= i32::MAX as f64 {
NativeResult::Ok(JsValue::int(acc as i32))
} else {
NativeResult::Ok(JsValue::float(acc))
}
}
/// 按 StrDecimalLiteral 文法扫描最长合法十进制数字前缀:数字 + 至多一个
/// '.'('.' 后必须有数字,整数部分可为空)+ 可选指数(e/E 可带符号,
/// mantissa 须先有数字,指数须有数字)。
///
/// # 边界与前提
/// - 前缀值经 fast_float 正确舍入解析(溢出饱和为 ±Infinity);
/// - 无数字前缀(空串、'.'、'abc'、'1e')返回 None;
/// - 消费长度是前缀的字节长,调用方自取前缀语义(parseFloat)或全串语义
/// (消费长度须等于全长,如 ToNumber 门)。
pub(crate) fn parse_decimal_prefix(text: &str) -> Option<(f64, usize)> {
let b = text.as_bytes();
let mut i = 0usize;
let mut dot = false;
let mut mantissa_digits = 0usize;
let mut end = 0usize;
while i < b.len() {
let c = b[i];
if c.is_ascii_digit() {
mantissa_digits += 1;
end = i + 1;
i += 1;
} else if c == b'.' && !dot {
dot = true;
i += 1;
} else {
break;
}
}
// 指数部分:e/E [+/-] 数字,且 mantissa 必须先有数字;指数无数字则不含 'e'。
if i < b.len() && (b[i] == b'e' || b[i] == b'E') && mantissa_digits > 0 {
let mut j = i + 1;
if j < b.len() && (b[j] == b'+' || b[j] == b'-') {
j += 1;
}
if j < b.len() && b[j].is_ascii_digit() {
while j < b.len() && b[j].is_ascii_digit() {
j += 1;
}
end = j;
}
}
if mantissa_digits == 0 {
return None;
}
match fast_float::parse::<f64, _>(&text[..end]) {
Ok(v) => Some((v, end)),
Err(_) => None,
}
}
/// `parseFloat(string)`:解析字符串前缀为浮点数。
///
/// 规范白名单 trim 后读 `+`/`-` 符号,特判精确大小写的 `Infinity`;随后取
/// 共享前缀扫描核([`parse_decimal_prefix`])的最长合法十进制前缀(`0x10`
/// 在 'x' 处停止得 0,`1.2.3` 得 1.2),无合法前缀返回 NaN。
pub fn number_parse_float<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::float(f64::NAN));
}
// 参数按 ToString 完整转换:对象经 ToPrimitive(string hint),Symbol 抛
// TypeError;对象方法抛出的原生异常原样传播。
let s = match oxide_runtime_api::to_string_full(vm.reg(args[1]), vm) {
Ok(s) => s,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
};
let s = s.trim_start_matches(is_js_ws).trim_end_matches(is_js_ws);
// 读符号;`Infinity` 大小写敏感,其后可带任意后缀(取最长合法前缀)。
let (neg, rest) = match s.as_bytes().first() {
Some(b'-') => (true, &s[1..]),
Some(b'+') => (false, &s[1..]),
_ => (false, s),
};
if rest.starts_with("Infinity") {
return NativeResult::Ok(JsValue::float(if neg { f64::NEG_INFINITY } else { f64::INFINITY }));
}
match parse_decimal_prefix(rest) {
Some((value, _)) => NativeResult::Ok(JsValue::float(if neg { -value } else { value })),
None => NativeResult::Ok(JsValue::float(f64::NAN)),
}
}
/// thisNumberValue(spec 21.7.3 共用步 1):Number 原始值直返;带
/// [[NumberData]] 的 Number 对象取被包值;其余一律 TypeError。
/// toFixed/toExponential/toPrecision/toString 四方法均以本助手起步,
/// 取代 ToObject 语义的 coerce(后者对 {} 静默装箱,不抛错)。
fn this_number_value<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<f64, JsValue> {
if this_val.is_int() || this_val.is_double() {
return Ok(oxide_runtime_api::to_number(this_val));
}
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if !ptr.is_null() {
// Number.prototype 本身即 [[NumberData]] = +0 的 Number 对象;
// 与 number_value_of 的 proto 特判同形。
let number_proto = vm.builtin_proto(ProtoKind::NumberProto);
if std::ptr::eq(ptr, number_proto) {
return Ok(0.0);
}
let obj = unsafe { &*ptr };
if obj.is_number_obj() {
return Ok(oxide_runtime_api::to_number(obj.boxed_value()));
}
}
}
Err(crate::error::create_type_error(vm, "Cannot convert this value to a number"))
}
/// ToIntegerOrInfinity 数值核(前置完整 ToNumber 后调用):NaN/±0 归 0,
/// ±∞ 取原值,其余向零截断。
fn to_integer_or_infinity(n: f64) -> 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`
/// 不传播异常,不得用于参数位。
fn coerce_to_integer_or_infinity<H: VmHost>(vm: &mut H, arg: JsValue) -> Result<f64, JsValue> {
let raw = match vm.coerce_number_bounded(arg) {
Ok(n) => n,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_type_error(vm, "Cannot convert argument to a number"));
}
};
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^1024)。
#[derive(Clone)]
struct Dec(Vec<u32>);
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))。q 为 32 整数倍时低 q 位恰为前 wm 整 limb,
/// limb wm 整体丢弃(先置 0 再由 trim 剥除),与 bm ≠ 0 臂统一截断。
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;
self.0[wm] = if bm != 0 { self.0[wm] & ((1u32 << bm) - 1) } else { 0 };
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<u8>, 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, -1074)
} else {
// 正规:显式隐含位补齐尾数(指数位先掩掉,勿随位模式带入)。
(((b & ((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<u8> = Vec::new();
let mut e: Option<i32> = 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<u8>, 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 参数忽略,恒按十进制输出。
/// 与 toString 的 radix-10 路径同形;不读第二个实参(length 为 0)。
pub fn number_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = match this_number_value(vm, vm.reg(args[0])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)))
}
/// `Number.prototype.toString(radix)`:按指定进制(2..36)转字符串。
///
/// 步 1 先 thisNumberValue(非 Number 原始值/对象抛 TypeError);十进制走共享的
/// ECMA-262 Number::toString 格式化;非十进制对截断后的整数部分做进制转换
/// (小数部分按近似处理)。radix 经 ToInteger 后越界抛 RangeError,
/// NaN/Infinity 输出专名。
pub fn number_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = match this_number_value(vm, vm.reg(args[0])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
let radix = if args.len() > 1 {
let radix_arg = vm.reg(args[1]);
if radix_arg.is_undefined() {
10u32
} else {
// radix 走 ToIntegerOrInfinity:先经对象 coercion(poisoned valueOf
// 需传播其异常),NaN/±0 归 0 后越界抛 RangeError。
let raw = match vm.coerce_number_bounded(radix_arg) {
Ok(n) => n,
Err(_) => {
// coercion 触发用户 valueOf/toString 抛出的异常经
// last_uncaught_value 恢复后原样重新抛出。
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert radix to a number"));
}
};
let r = to_integer_or_infinity(raw);
if !(2.0..=36.0).contains(&r) {
return NativeResult::Err(crate::error::create_range_error(
vm,
"toString() radix must be between 2 and 36",
));
}
r as u32
}
} else {
10u32
};
if radix == 10 {
return NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)));
}
if n.is_nan() {
return NativeResult::Ok(vm.new_string("NaN"));
}
if n.is_infinite() {
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
}
if n.abs() >= u128::MAX as f64 {
// 超出 u128 可精确表示的整数范围,退化为十进制近似。
return NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)));
}
let neg = n.is_sign_negative();
let mut value = n.abs().trunc() as u128;
let mut result = String::new();
if value == 0 {
result.push('0');
} else {
let chars = b"0123456789abcdefghijklmnopqrstuvwxyz";
let mut digits = Vec::new();
while value > 0 {
digits.push(chars[(value % radix as u128) as usize] as char);
value /= radix as u128;
}
for ch in digits.iter().rev() {
result.push(*ch);
}
}
if neg {
result.insert(0, '-');
}
NativeResult::Ok(vm.new_string_owned(result))
}
/// `Number.prototype.toFixed(digits)`:固定小数位数(0..100)输出字符串,
/// 超出范围抛 RangeError;NaN/Infinity 输出专名。
///
/// 步序:先 thisNumberValue,再转换 fractionDigits 并做范围校验(范围检查
/// 先于 NaN 短路,NaN.toFixed(Infinity) 须抛 RangeError);随后 x ≥ 10^21
/// 退化为 String(x) 科学式(±Infinity 同形,经此分支输出专名);其余按精确
/// digit 串 half-up 舍入渲染(tie 取较大者,Rust 格式化 half-even 不可用)。
pub fn number_to_fixed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = match this_number_value(vm, vm.reg(args[0])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
let raw = if args.len() > 1 {
match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
}
} else {
0.0
};
if !(0.0..=100.0).contains(&raw) {
return NativeResult::Err(crate::error::create_range_error(
vm,
"toFixed() fractionDigits must be between 0 and 100",
));
}
if n.is_nan() {
return NativeResult::Ok(vm.new_string("NaN"));
}
// x ≥ 10^21 用科学式 String 形态(±Infinity 同为该形态)。
if n.abs() >= 1e21 {
let s = if n < 0.0 {
format!("-{}", oxide_runtime_api::js_number_to_string(-n))
} else {
oxide_runtime_api::js_number_to_string(n)
};
return NativeResult::Ok(vm.new_string_owned(s));
}
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。
pub fn number_is_integer<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
if !val.is_int() && !val.is_double() {
return NativeResult::Ok(JsValue::bool(false));
}
let n = oxide_runtime_api::to_number(val);
NativeResult::Ok(JsValue::bool(n.trunc() == n && n.is_finite()))
}
/// `Number.isSafeInteger`:参数是安全整数范围(±2^53-1)内的整数才返回 true。
pub fn number_is_safe_integer<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
if !val.is_int() && !val.is_double() {
return NativeResult::Ok(JsValue::bool(false));
}
let n = oxide_runtime_api::to_number(val);
let safe = n.trunc() == n && n.is_finite() && n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64;
NativeResult::Ok(JsValue::bool(safe))
}
/// `Number.prototype.toPrecision(precision)`:按有效数字位数(1..100)输出,
/// 科学计数法与定点表示按指数自动切换,超范围抛 RangeError。
///
/// 步序:先 thisNumberValue;precision 缺省直接返 ToString(x)(含 NaN/±∞
/// 全形);再转 precision,NaN/±∞ 专名先于范围校验((±∞).toPrecision(1000)
/// 返 "Infinity" 而非 RangeError);最后按精确 digit 串 half-up 舍入,
/// 定点/指数分界用**进位后**的首位指数(999.toPrecision(2) = "1.0e+3")。
pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = match this_number_value(vm, vm.reg(args[0])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
if args.len() <= 1 || vm.reg(args[1]).is_undefined() {
return NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)));
}
let raw = match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
if n.is_nan() {
return NativeResult::Ok(vm.new_string("NaN"));
}
if n.is_infinite() {
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
}
if !(1.0..=100.0).contains(&raw) {
return NativeResult::Err(crate::error::create_range_error(
vm,
"toPrecision() precision must be between 1 and 100",
));
}
let precision = raw as usize;
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');
}
}
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/±∞ 专名先于范围检查;缺省位数走最短 round-trip
/// 前缀,显式位数按精确 digit 串 half-up 舍入(tie 取较大者);指数恒带符号。
pub fn number_to_exponential<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n = match this_number_value(vm, vm.reg(args[0])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
};
let raw = if args.len() > 1 && !vm.reg(args[1]).is_undefined() {
match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
Ok(v) => v,
Err(exc) => return NativeResult::Err(exc),
}
} else {
f64::NAN
};
if n.is_nan() {
return NativeResult::Ok(vm.new_string("NaN"));
}
if n.is_infinite() {
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
}
let has_digits = !raw.is_nan();
if has_digits && !(0.0..=100.0).contains(&raw) {
return NativeResult::Err(crate::error::create_range_error(
vm,
"toExponential() fractionDigits must be between 0 and 100",
));
}
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 {
// 缺省位数 = Rust {:e} 最短 round-trip 形(规范"最短精确有效数字数"
// 同口径),仅补指数符号。
let mut s = format!("{:e}", n.abs());
if let Some(e_pos) = s.find('e') {
if !s[e_pos + 1..].starts_with('-') {
s.insert(e_pos + 1, '+');
}
}
let result = format!("{}{}", if neg { "-" } else { "" }, s);
return NativeResult::Ok(vm.new_string_owned(result));
}
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;
/// 原始 number 直接返回,非 Number 对象抛 TypeError。
pub fn number_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if this_val.is_int() || this_val.is_double() {
return NativeResult::Ok(this_val);
}
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if !ptr.is_null() {
// Number.prototype 本身是 Number 对象,其 [[NumberData]] 为 +0。
let number_proto = vm.builtin_proto(ProtoKind::NumberProto);
if ptr == number_proto {
return NativeResult::Ok(JsValue::int(0));
}
let obj = unsafe { &*ptr };
if obj.is_number_obj() {
return NativeResult::Ok(obj.boxed_value());
}
}
}
NativeResult::Err(crate::error::create_type_error(
vm,
"Number.prototype.valueOf called on incompatible receiver",
))
}