mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: Number 静态方法 ToNumber 转换、格式化方法精度/边界修复、常量属性描述符
This commit is contained in:
@@ -50,26 +50,15 @@ 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_double() {
|
||||
NativeResult::Ok(JsValue::bool(val.as_double().is_nan()))
|
||||
} else {
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
let n = oxide_runtime_api::to_number(vm.reg(args[1]));
|
||||
NativeResult::Ok(JsValue::bool(n.is_nan()))
|
||||
}
|
||||
|
||||
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() {
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
if val.is_double() {
|
||||
return NativeResult::Ok(JsValue::bool(val.as_double().is_finite()));
|
||||
}
|
||||
let n = oxide_runtime_api::to_number(val);
|
||||
let n = oxide_runtime_api::to_number(vm.reg(args[1]));
|
||||
NativeResult::Ok(JsValue::bool(n.is_finite()))
|
||||
}
|
||||
|
||||
@@ -184,64 +173,122 @@ pub fn number_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn number_to_fixed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let digits = if args.len() > 1 {
|
||||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
|
||||
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 fraction_digits = if args.len() > 1 {
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
if !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toFixed() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
}
|
||||
raw as usize
|
||||
} else {
|
||||
0usize
|
||||
}
|
||||
.min(20);
|
||||
|
||||
let formatted = format!("{:.digits$}", n);
|
||||
};
|
||||
let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n };
|
||||
let formatted = format!("{:.precision$}", n_abs, precision = fraction_digits);
|
||||
NativeResult::Ok(vm.new_string(&formatted))
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn number_to_exponential<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let digits = if args.len() > 1 {
|
||||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
|
||||
} else {
|
||||
0usize
|
||||
pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() <= 1 {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toPrecision() requires a precision argument between 1 and 100",
|
||||
));
|
||||
}
|
||||
.min(100);
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
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;
|
||||
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 formatted = format!("{:.digits$e}", n);
|
||||
NativeResult::Ok(vm.new_string(&formatted))
|
||||
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 {
|
||||
format!("{:.dec$e}", n_abs, dec = precision - 1)
|
||||
};
|
||||
if is_neg {
|
||||
NativeResult::Ok(vm.new_string(&format!("-{}", formatted)))
|
||||
} else {
|
||||
NativeResult::Ok(vm.new_string(&formatted))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub fn number_to_exponential<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let precision = if args.len() > 1 {
|
||||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
|
||||
} else {
|
||||
0usize
|
||||
}
|
||||
.min(21);
|
||||
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 formatted = format!("{:.precision$}", n);
|
||||
NativeResult::Ok(vm.new_string(&formatted))
|
||||
let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n.abs() };
|
||||
let sign_prefix = if n.is_sign_negative() && !n.is_nan() && !(n == 0.0) { "-" } else { "" };
|
||||
if args.len() <= 1 {
|
||||
let formatted = format!("{:e}", n_abs);
|
||||
let mut s = formatted;
|
||||
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..]);
|
||||
}
|
||||
}
|
||||
let result = format!("{}{}", sign_prefix, s);
|
||||
return NativeResult::Ok(vm.new_string(&result));
|
||||
}
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
if !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toExponential() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
}
|
||||
let fraction_digits = raw as usize;
|
||||
let formatted = format!("{:.digits$e}", n_abs, digits = fraction_digits);
|
||||
NativeResult::Ok(vm.new_string(&format!("{}{}", sign_prefix, formatted)))
|
||||
}
|
||||
|
||||
pub fn number_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use oxide_runtime_api::to_number;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
#[test]
|
||||
fn is_nan_nan_returns_true() {
|
||||
assert!(to_number(JsValue::float(f64::NAN)).is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_nan_number_123_returns_false() {
|
||||
assert!(!to_number(JsValue::int(123)).is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_nan_undefined_returns_true() {
|
||||
assert!(to_number(JsValue::undefined()).is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_finite_number_42_returns_true() {
|
||||
assert!(to_number(JsValue::int(42)).is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_finite_infinity_returns_false() {
|
||||
assert!(!to_number(JsValue::float(f64::INFINITY)).is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_integer_non_number_float_nan() {
|
||||
let val = JsValue::undefined();
|
||||
assert!(!val.is_int() && !val.is_double());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_integer_int_42() {
|
||||
let val = JsValue::int(42);
|
||||
assert!(val.is_int());
|
||||
let n = to_number(val);
|
||||
assert!(n.trunc() == n && n.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_integer_double_fractional() {
|
||||
let val = JsValue::float(42.5);
|
||||
assert!(val.is_double());
|
||||
let n = to_number(val);
|
||||
assert!(!(n.trunc() == n && n.is_finite()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_safe_integer_max_value() {
|
||||
let val = JsValue::float(9007199254740991f64);
|
||||
let n = to_number(val);
|
||||
assert!(n.trunc() == n && n.is_finite());
|
||||
assert!(n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_safe_integer_overflow() {
|
||||
let val = JsValue::float(9007199254740992f64);
|
||||
let n = to_number(val);
|
||||
assert!(n.trunc() == n && n.is_finite());
|
||||
assert!(!(n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64));
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use crate::bind_constructor;
|
||||
use crate::bindings::{apply_binding_table, configure_native_constructor};
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
pub fn bind_number(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
@@ -27,6 +27,8 @@ pub fn bind_number(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
oxide_builtins::number::number_is_safe_integer::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
("parseInt", oxide_builtins::number::number_parse_int::<crate::vm::Vm> as *const (), 1),
|
||||
("parseFloat", oxide_builtins::number::number_parse_float::<crate::vm::Vm> as *const (), 1),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -62,9 +64,11 @@ pub fn bind_number(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
("POSITIVE_INFINITY", JsValue::float(f64::INFINITY)),
|
||||
] {
|
||||
ctor.ensure_hash_props().push(value);
|
||||
let pos = ctor.ensure_hash_props().len() - 1;
|
||||
let prop_si = core.perm_interner().intern(name).0;
|
||||
let next_shape = core.shape_forge().make_shape(ctor.shape_id(), prop_si);
|
||||
ctor.set_shape_id(next_shape);
|
||||
ctor.set_data_meta(pos, PropAttributes::new(false, false, false));
|
||||
}
|
||||
|
||||
bind_constructor!(core, global, "Number", ctor_ptr, oxide_builtins::number::number_constructor::<crate::vm::Vm>, 1, hash: true);
|
||||
|
||||
Reference in New Issue
Block a user