Skip to content

Commit b3b3898

Browse files
authored
fix(ecmascript): Fix BigInt and Number equality comparison precision (#907)
1 parent bc85744 commit b3b3898

File tree

5 files changed

+64
-54
lines changed

5 files changed

+64
-54
lines changed

nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,9 +707,13 @@ pub(crate) fn is_loosely_equal<'a>(
707707
}
708708

709709
// b. If ℝ(x) = ℝ(y), return true; otherwise return false.
710-
let a = a.to_real(agent);
711710
let b = b.to_real(agent);
712-
return Ok(a == b);
711+
712+
// Compare BigInt with f64 using precise comparison.
713+
return Ok(match a {
714+
BigInt::BigInt(heap_big_int) => agent[heap_big_int] == b,
715+
BigInt::SmallBigInt(small_big_int) => small_big_int.into_i64() as f64 == b,
716+
});
713717
}
714718

715719
// 14. Return false.

nova_vm/src/ecmascript/types/language/bigint.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -734,23 +734,6 @@ impl<'a> BigInt<'a> {
734734
gc,
735735
)
736736
}
737-
738-
pub(crate) fn to_real(self, agent: &mut Agent) -> f64 {
739-
match self {
740-
BigInt::BigInt(heap_big_int) => {
741-
let mut value = 0f64;
742-
for (i, digits) in agent[heap_big_int].data.iter_u64_digits().enumerate() {
743-
if i == 0 {
744-
value += digits as f64;
745-
} else {
746-
value += ((digits as u128) << (i * 64)) as f64;
747-
}
748-
}
749-
value
750-
}
751-
BigInt::SmallBigInt(small_big_int) => small_big_int.into_i64() as f64,
752-
}
753-
}
754737
}
755738

756739
bindable_handle!(BigInt);

nova_vm/src/ecmascript/types/language/bigint/data.rs

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,35 @@ pub struct BigIntHeapData {
1212
pub(crate) data: BigInt,
1313
}
1414

15+
/// Convert f64 to exact BigInt for precise comparison.
16+
/// f64 (IEEE 754 double): 1 sign bit, 11 exponent bits, 52 mantissa bits.
17+
fn f64_to_exact_bigint(value: f64) -> BigInt {
18+
if value == 0.0 {
19+
return BigInt::from(0);
20+
}
21+
22+
let bits = value.to_bits();
23+
let sign_bit = bits >> 63;
24+
let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
25+
let mantissa_bits = bits & 0xfffffffffffff;
26+
27+
// Actual exponent (subtract bias of 1023)
28+
let exponent = exponent_bits - 1023;
29+
// Mantissa with implicit leading 1 bit
30+
let mantissa = mantissa_bits | 0x10000000000000u64;
31+
32+
// The value is mantissa * 2^(exponent - 52)
33+
let shift = exponent - 52;
34+
35+
let result = if shift >= 0 {
36+
BigInt::from(mantissa) << (shift as usize)
37+
} else {
38+
BigInt::from(mantissa) >> ((-shift) as usize)
39+
};
40+
41+
if sign_bit == 1 { -result } else { result }
42+
}
43+
1544
impl HeapMarkAndSweep for BigIntHeapData {
1645
#[inline(always)]
1746
fn mark_values(&self, _queues: &mut WorkQueues) {
@@ -30,37 +59,37 @@ impl PartialEq<f64> for BigIntHeapData {
3059
// Cannot be equal to non-integer.
3160
return false;
3261
}
33-
let mut base: f64 = match self.data.sign() {
34-
Sign::Minus => -1.0,
35-
// We should never have 0 value BigIntHeapData.
36-
Sign::NoSign => unreachable!(),
37-
Sign::Plus => 1.0,
38-
};
39-
self.data
40-
.iter_u64_digits()
41-
.enumerate()
42-
.for_each(|(index, digit)| {
43-
base += (digit as f64) * 2f64.powi(index as i32);
44-
});
45-
base == *other
62+
self.data == f64_to_exact_bigint(*other)
4663
}
4764
}
4865

4966
impl PartialOrd<f64> for BigIntHeapData {
5067
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
51-
let mut base: f64 = match self.data.sign() {
52-
Sign::Minus => -1.0,
53-
// We should never have 0 value BigIntHeapData.
54-
Sign::NoSign => unreachable!(),
55-
Sign::Plus => 1.0,
56-
};
57-
self.data
58-
.iter_u64_digits()
59-
.enumerate()
60-
.for_each(|(index, digit)| {
61-
base += (digit as f64) * 2f64.powi(index as i32);
62-
});
63-
base.partial_cmp(other)
68+
if other.is_nan() {
69+
return None;
70+
}
71+
if *other == f64::INFINITY {
72+
return Some(Ordering::Less);
73+
}
74+
if *other == f64::NEG_INFINITY {
75+
return Some(Ordering::Greater);
76+
}
77+
// For non-integer f64, compare with truncated value and adjust
78+
if other.trunc() != *other {
79+
let truncated = f64_to_exact_bigint(other.trunc());
80+
return match self.data.cmp(&truncated) {
81+
Ordering::Equal => {
82+
// BigInt equals truncated value, so compare with fractional part
83+
if *other > 0.0 {
84+
Some(Ordering::Less) // e.g., 5n < 5.5
85+
} else {
86+
Some(Ordering::Greater) // e.g., -5n > -5.5
87+
}
88+
}
89+
ord => Some(ord),
90+
};
91+
}
92+
Some(self.data.cmp(&f64_to_exact_bigint(*other)))
6493
}
6594
}
6695

tests/expectations.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6269,7 +6269,6 @@
62696269
"language/expressions/delete/11.4.1-4.a-6.js": "FAIL",
62706270
"language/expressions/delete/super-property-uninitialized-this.js": "FAIL",
62716271
"language/expressions/division/S11.5.2_A4_T10.js": "FAIL",
6272-
"language/expressions/does-not-equals/bigint-and-number-extremes.js": "CRASH",
62736272
"language/expressions/dynamic-import/assignment-expression/await-identifier.js": "FAIL",
62746273
"language/expressions/dynamic-import/catch/nested-arrow-import-catch-eval-script-code-target.js": "FAIL",
62756274
"language/expressions/dynamic-import/catch/nested-arrow-import-catch-import-source-source-text-module.js": "UNRESOLVED",
@@ -6414,7 +6413,6 @@
64146413
"language/expressions/dynamic-import/usage/nested-while-import-then-eval-script-code-host-resolves-module-code.js": "FAIL",
64156414
"language/expressions/dynamic-import/usage/syntax-nested-block-labeled-eval-script-code-host-resolves-module-code.js": "FAIL",
64166415
"language/expressions/dynamic-import/usage/top-level-import-then-eval-script-code-host-resolves-module-code.js": "FAIL",
6417-
"language/expressions/equals/bigint-and-number-extremes.js": "CRASH",
64186416
"language/expressions/exponentiation/applying-the-exp-operator_A6.js": "FAIL",
64196417
"language/expressions/function/static-init-await-binding.js": "FAIL",
64206418
"language/expressions/function/static-init-await-reference.js": "FAIL",
@@ -6424,20 +6422,16 @@
64246422
"language/expressions/greater-than-or-equal/S11.8.4_A4.12_T1.js": "FAIL",
64256423
"language/expressions/greater-than-or-equal/bigint-and-bigint.js": "FAIL",
64266424
"language/expressions/greater-than-or-equal/bigint-and-incomparable-string.js": "FAIL",
6427-
"language/expressions/greater-than-or-equal/bigint-and-number-extremes.js": "FAIL",
64286425
"language/expressions/greater-than/S11.8.2_A4.12_T1.js": "FAIL",
64296426
"language/expressions/greater-than/bigint-and-bigint.js": "FAIL",
64306427
"language/expressions/greater-than/bigint-and-incomparable-string.js": "FAIL",
6431-
"language/expressions/greater-than/bigint-and-number-extremes.js": "FAIL",
64326428
"language/expressions/in/private-field-rhs-await-absent.js": "FAIL",
64336429
"language/expressions/less-than-or-equal/S11.8.3_A4.12_T1.js": "FAIL",
64346430
"language/expressions/less-than-or-equal/bigint-and-bigint.js": "FAIL",
64356431
"language/expressions/less-than-or-equal/bigint-and-incomparable-string.js": "FAIL",
6436-
"language/expressions/less-than-or-equal/bigint-and-number-extremes.js": "FAIL",
64376432
"language/expressions/less-than/S11.8.1_A4.12_T1.js": "FAIL",
64386433
"language/expressions/less-than/bigint-and-bigint.js": "FAIL",
64396434
"language/expressions/less-than/bigint-and-incomparable-string.js": "FAIL",
6440-
"language/expressions/less-than/bigint-and-number-extremes.js": "FAIL",
64416435
"language/expressions/logical-and/tco-right.js": "FAIL",
64426436
"language/expressions/logical-or/tco-right.js": "FAIL",
64436437
"language/expressions/multiplication/S11.5.1_A4_T7.js": "FAIL",

tests/metrics.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"results": {
3-
"crash": 72,
4-
"fail": 7455,
5-
"pass": 39825,
3+
"crash": 70,
4+
"fail": 7451,
5+
"pass": 39831,
66
"skip": 3326,
77
"timeout": 18,
88
"unresolved": 37

0 commit comments

Comments
 (0)