fix: BigInt 构造器精确大整数与 new/无参/分隔符语义

This commit is contained in:
2026-09-24 01:58:03 +00:00
parent a0db0c5c2b
commit a5663b439f
3 changed files with 134 additions and 17 deletions
+33 -16
View File
@@ -2,23 +2,17 @@ use num_bigint::BigInt;
use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost};
use oxide_types::value::JsValue;
/// JS `BigInt(value)` 构造逻辑:
/// - `new BigInt()` 抛 TypeError(BigInt 不可 new)
/// - 无参 → `0n`
/// - 其余参数先 ToPrimitive(number),Number 走 NumberToBigInt,其余走 ToBigInt。
/// JS `BigInt(value)` 构造逻辑(§21.2.1.1):
/// - new 形态无条件抛 TypeError(BigInt 不可 new);
/// - 无参走 ToBigInt(undefined) → TypeError;
/// - Number 参数走 NumberToBigInt(非整数抛 RangeError,幅值无 2^53 上界),
/// 其余参数走 ToBigInt。
pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() > 1 {
let this_val = vm.reg(args[0]);
if this_val.is_object() {
// new 语义:BigInt 不可 new,抛 TypeError。
return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor"));
}
if vm.constructing_native() {
return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor"));
}
// 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。
if args.len() <= 1 {
return NativeResult::Ok(vm.new_bigint(BigInt::from(0)));
}
let val = vm.reg(args[1]);
// 无参 BigInt() 等价于 ToBigInt(undefined) → TypeError,与显式 undefined 同路。
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
// ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString
// 抛出的异常原样传播,且装箱转换的异常只触发一次。
let prim = match to_primitive(val, ToPrimitiveHint::Number, vm) {
@@ -42,7 +36,7 @@ pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
"The number cannot be converted to a BigInt because it is not an integer",
));
}
return NativeResult::Ok(vm.new_bigint(BigInt::from(d.trunc() as i128)));
return NativeResult::Ok(vm.new_bigint(integer_double_to_bigint(d)));
}
// 步骤 4:其它原始类型走 ToBigInt。
match to_bigint(vm, prim) {
@@ -51,6 +45,29 @@ pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
}
/// NumberToBigInt 步骤 3:整数 double 的精确值 ℤ(x)。
/// |x| < 2^53 由尾数右移指数位直转;|x| ≥ 2^53 位分解(尾数左移 exp-1075 位),
/// 全程精确无饱和。-0 返回 0(BigInt 无负零)。
fn integer_double_to_bigint(d: f64) -> BigInt {
let bits = d.to_bits();
let negative = bits >> 63 != 0;
let exp_field = (bits >> 52) & 0x7FF;
let significand = bits & 0xF_FFFF_FFFF_FFFF;
if exp_field == 0 {
return BigInt::from(0);
}
let mag = if exp_field < 1075 {
BigInt::from((0x10_0000_0000_0000u64 + significand) >> (1075 - exp_field))
} else {
BigInt::from(0x10_0000_0000_0000u64 + significand) << (exp_field - 1075)
};
if negative {
-mag
} else {
mag
}
}
/// `BigInt.prototype.toString(radix)`:thisBigIntValue 后按 radix 输出。
pub fn bigint_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
@@ -0,0 +1,91 @@
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
vm.run(&Arc::new(module))
}
/// 求值一个应得布尔结果的表达式并断言其类型。
fn eval_bool(source: &str) -> bool {
let result = eval(source).expect("eval should succeed");
assert!(result.is_bool(), "expected boolean result, got {result:?}");
result.as_bool()
}
// --- Number 参数:NumberToBigInt 精确值 ---
/// 1e300 的 double 精确值(f64 位分解的整数部分),非十进制 10^300。
const ONE_E300: &str = "1000000000000000052504760255204420248704468581108159154915854115511802457988908195786371375080447864043704443832883878176942523235360430575644792184786706982848387200926575803737830233794788090059368953234970799945081119038967640880074652742780142494579258788820056842838115669472196386865459400540160";
#[test]
fn double_1e300_exact_value() {
assert!(eval_bool(&format!("BigInt(1e300) === {ONE_E300}n")));
assert!(eval_bool(&format!("BigInt(-1e300) === -{ONE_E300}n")));
}
#[test]
fn double_two_pow_53_exact_value() {
assert!(eval_bool("BigInt(9007199254740992) === 9007199254740992n"));
assert!(eval_bool("BigInt(9007199254740991.0) === 9007199254740991n"));
}
#[test]
fn double_between_two_pow_67_exact_value() {
assert!(eval_bool("BigInt(2.5e20) === 250000000000000000000n"));
assert!(eval_bool("BigInt(3500000000000000.0) === 3500000000000000n"));
}
#[test]
fn double_negative_zero_is_zero_bigint() {
assert!(eval_bool("BigInt(-0.0) === 0n"));
}
// --- 构造形态:new 无条件 TypeError ---
#[test]
fn new_bigint_no_arg_throws_type_error() {
assert!(eval_bool("try { new BigInt(); 0 } catch (e) { e instanceof TypeError }"));
}
#[test]
fn new_bigint_with_value_throws_type_error() {
assert!(eval_bool("try { new BigInt(1); 0 } catch (e) { e instanceof TypeError }"));
}
// --- 无参:ToBigInt(undefined) → TypeError ---
#[test]
fn no_arg_throws_type_error() {
assert!(eval_bool("try { BigInt(); 0 } catch (e) { e instanceof TypeError }"));
}
#[test]
fn explicit_undefined_throws_type_error() {
assert!(eval_bool("try { BigInt(undefined); 0 } catch (e) { e instanceof TypeError }"));
}
// --- 字符串参数:数字分隔符非法 ---
#[test]
fn decimal_digit_separator_syntax_error() {
assert!(eval_bool("try { BigInt(\"1_0\"); 0 } catch (e) { e instanceof SyntaxError }"));
}
#[test]
fn hex_digit_separator_syntax_error() {
assert!(eval_bool("try { BigInt(\"0x1_F\"); 0 } catch (e) { e instanceof SyntaxError }"));
}
#[test]
fn plain_string_forms_unaffected() {
assert!(eval_bool("BigInt(\"10\") === 10n"));
assert!(eval_bool("BigInt(\"0x1F\") === 31n"));
assert!(eval_bool("BigInt(\" 0o17 \") === 15n"));
}
+10 -1
View File
@@ -960,7 +960,16 @@ fn string_to_bigint_full<H: VmHost>(host: &mut H, s: &str) -> Result<JsValue, St
(10u32, rest)
};
if digits.is_empty() {
return Err(host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal"));
return Err(
host.error_message_text("SyntaxError", "Cannot convert string to a BigInt: invalid integer literal")
);
}
// StringIntegerLiteral 四种基数语法均不含数字分隔符;parse_bytes 会接受
// 下划线,故先显式拒绝。
if digits.contains('_') {
return Err(
host.error_message_text("SyntaxError", "Cannot convert string to a BigInt: invalid integer literal")
);
}
let m = num_bigint::BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| {
host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal")