Skip to main content

boa_engine/
bigint.rs

1//! Boa's implementation of ECMAScript's bigint primitive type.
2
3use crate::{JsData, JsResult, JsString, builtins::Number, error::JsNativeError};
4use boa_gc::{Finalize, Trace};
5use num_bigint::Sign;
6use num_integer::Integer;
7use num_traits::{FromPrimitive, One, ToPrimitive, Zero, pow::Pow};
8use std::{
9    fmt::{self, Display},
10    ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
11    ptr::NonNull,
12    rc::Rc,
13};
14
15/// The raw bigint type.
16pub type RawBigInt = num_bigint::BigInt;
17
18#[cfg(feature = "deser")]
19use serde::{Deserialize, Serialize};
20
21/// JavaScript bigint primitive rust type.
22#[allow(
23    clippy::unsafe_derive_deserialize,
24    reason = "unsafe methods do not add invariants that need to be held"
25)]
26#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
27#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Trace, Finalize, JsData)]
28// Safety: `JsBigInt` doesn't contain any traceable types.
29#[boa_gc(unsafe_empty_trace)]
30pub struct JsBigInt {
31    inner: Rc<RawBigInt>,
32}
33
34impl JsBigInt {
35    /// Create a new [`JsBigInt`].
36    #[must_use]
37    pub fn new<T: Into<Self>>(value: T) -> Self {
38        value.into()
39    }
40
41    /// Create a [`JsBigInt`] with value `0`.
42    #[inline]
43    #[must_use]
44    pub fn zero() -> Self {
45        Self {
46            inner: Rc::new(RawBigInt::zero()),
47        }
48    }
49
50    /// Check if is zero.
51    #[inline]
52    #[must_use]
53    pub fn is_zero(&self) -> bool {
54        self.inner.is_zero()
55    }
56
57    /// Create a [`JsBigInt`] with value `1`.
58    #[inline]
59    #[must_use]
60    pub fn one() -> Self {
61        Self {
62            inner: Rc::new(RawBigInt::one()),
63        }
64    }
65
66    /// Check if is one.
67    #[inline]
68    #[must_use]
69    pub fn is_one(&self) -> bool {
70        self.inner.is_one()
71    }
72
73    /// Convert bigint to string with radix.
74    #[inline]
75    #[must_use]
76    pub fn to_string_radix(&self, radix: u32) -> String {
77        self.inner.to_str_radix(radix)
78    }
79
80    /// Converts the `BigInt` to a f64 type.
81    ///
82    /// Returns `f64::INFINITY` if the `BigInt` is too big.
83    #[inline]
84    #[must_use]
85    pub fn to_f64(&self) -> f64 {
86        self.inner.to_f64().unwrap_or(f64::INFINITY)
87    }
88
89    /// Converts the `BigInt` to a i128 type.
90    ///
91    /// Returns `i128::MAX` if the `BigInt` is too big.
92    #[inline]
93    #[must_use]
94    pub fn to_i128(&self) -> i128 {
95        self.inner.to_i128().unwrap_or(i128::MAX)
96    }
97
98    /// Converts a string to a `BigInt` with the specified radix.
99    #[inline]
100    #[must_use]
101    pub fn from_string_radix(buf: &str, radix: u32) -> Option<Self> {
102        Some(Self {
103            inner: Rc::new(RawBigInt::parse_bytes(buf.as_bytes(), radix)?),
104        })
105    }
106
107    /// Abstract operation `StringToBigInt ( str )`
108    ///
109    /// More information:
110    /// - [ECMAScript reference][spec]
111    ///
112    /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint
113    pub(crate) fn from_js_string(string: &JsString) -> Option<JsBigInt> {
114        // 1. Let text be ! StringToCodePoints(str).
115        // 2. Let literal be ParseText(text, StringIntegerLiteral).
116        // 3. If literal is a List of errors, return undefined.
117        // 4. Let mv be the MV of literal.
118        // 5. Assert: mv is an integer.
119        // 6. Return ℤ(mv).
120        JsBigInt::from_string(string.to_std_string().ok().as_ref()?)
121    }
122
123    /// This function takes a string and converts it to `BigInt` type.
124    ///
125    /// More information:
126    ///  - [ECMAScript reference][spec]
127    ///
128    /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint
129    #[inline]
130    #[must_use]
131    pub fn from_string(mut string: &str) -> Option<Self> {
132        string = string.trim();
133
134        if string.is_empty() {
135            return Some(Self::zero());
136        }
137
138        let mut radix = 10;
139        if string.starts_with("0b") || string.starts_with("0B") {
140            radix = 2;
141            string = &string[2..];
142        } else if string.starts_with("0x") || string.starts_with("0X") {
143            radix = 16;
144            string = &string[2..];
145        } else if string.starts_with("0o") || string.starts_with("0O") {
146            radix = 8;
147            string = &string[2..];
148        }
149
150        Self::from_string_radix(string, radix)
151    }
152
153    /// Checks for `SameValueZero` equality.
154    ///
155    /// More information:
156    ///  - [ECMAScript reference][spec]
157    ///
158    /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal
159    #[inline]
160    #[must_use]
161    pub fn same_value_zero(x: &Self, y: &Self) -> bool {
162        // Return BigInt::equal(x, y)
163        Self::equal(x, y)
164    }
165
166    /// Checks for `SameValue` equality.
167    ///
168    ///
169    /// More information:
170    ///  - [ECMAScript reference][spec]
171    ///
172    /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue
173    #[inline]
174    #[must_use]
175    pub fn same_value(x: &Self, y: &Self) -> bool {
176        // Return BigInt::equal(x, y)
177        Self::equal(x, y)
178    }
179
180    /// Checks for mathematical equality.
181    ///
182    /// The abstract operation `BigInt::equal` takes arguments x (a `BigInt`) and y (a `BigInt`).
183    /// It returns `true` if x and y have the same mathematical integer value and false otherwise.
184    ///
185    /// More information:
186    ///  - [ECMAScript reference][spec]
187    ///
188    /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero
189    #[inline]
190    #[must_use]
191    pub fn equal(x: &Self, y: &Self) -> bool {
192        x == y
193    }
194
195    /// Returns `x` to the power `y`.
196    #[inline]
197    pub fn pow(x: &Self, y: &Self) -> JsResult<Self> {
198        let y = y
199            .inner
200            .to_biguint()
201            .ok_or_else(|| JsNativeError::range().with_message("BigInt negative exponent"))?;
202
203        let num_bits = (x.inner.bits() as f64
204            * y.to_f64().expect("Unable to convert from BigUInt to f64"))
205        .floor()
206            + 1f64;
207
208        if num_bits > 1_000_000_000f64 {
209            return Err(JsNativeError::range()
210                .with_message("Maximum BigInt size exceeded")
211                .into());
212        }
213
214        Ok(Self::new(x.inner.as_ref().clone().pow(y)))
215    }
216
217    /// Performs the `>>` operation.
218    #[inline]
219    pub fn shift_right(x: &Self, y: &Self) -> JsResult<Self> {
220        match y.inner.to_i32() {
221            Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shr(n as usize))),
222            Some(n) => Ok(Self::new(x.inner.as_ref().clone().shl(n.unsigned_abs()))),
223            // y doesn't fit in i32.
224            //
225            // Best-effort safeguard: while the spec doesn't explicitly mandate a
226            // result for implementation-limited shift amounts, the mathematical
227            // definition of BigInt right shift (`floor(x / 2^y)`) from
228            // <https://tc39.es/ecma262/#sec-numeric-types-bigint-signedRightShift>
229            // implies that for very large positive y the result converges to
230            // 0n (x >= 0) or -1n (x < 0). V8 and SpiderMonkey agree.
231            None => match (x.inner.sign(), y.inner.sign()) {
232                // x >> (large positive): all bits are shifted out.
233                (Sign::Minus, Sign::Plus) => Ok(Self::new(RawBigInt::from(-1))),
234                (_, Sign::Plus) => Ok(Self::zero()),
235                // x >> (large negative) is equivalent to x << (large positive), which overflows.
236                (_, _) => Err(JsNativeError::range()
237                    .with_message("Maximum BigInt size exceeded")
238                    .into()),
239            },
240        }
241    }
242
243    /// Performs the `<<` operation.
244    #[inline]
245    pub fn shift_left(x: &Self, y: &Self) -> JsResult<Self> {
246        match y.inner.to_i32() {
247            Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shl(n as usize))),
248            Some(n) => Ok(Self::new(x.inner.as_ref().clone().shr(n.unsigned_abs()))),
249            // y doesn't fit in i32.
250            //
251            // Best-effort safeguard: symmetric to shift_right above.
252            // See <https://tc39.es/ecma262/#sec-numeric-types-bigint-leftShift>.
253            None => match (x.inner.sign(), y.inner.sign()) {
254                // x << (large negative) is equivalent to x >> (large positive): all bits shifted out.
255                (Sign::Minus, Sign::Minus) => Ok(Self::new(RawBigInt::from(-1))),
256                (_, Sign::Minus) => Ok(Self::zero()),
257                // x << (large positive) overflows.
258                (_, _) => Err(JsNativeError::range()
259                    .with_message("Maximum BigInt size exceeded")
260                    .into()),
261            },
262        }
263    }
264
265    /// Floored integer modulo.
266    ///
267    /// # Examples
268    /// ```
269    /// # use num_integer::Integer;
270    /// assert_eq!((8).mod_floor(&3), 2);
271    /// assert_eq!((8).mod_floor(&-3), -1);
272    /// ```
273    #[inline]
274    #[must_use]
275    pub fn mod_floor(x: &Self, y: &Self) -> Self {
276        Self::new(x.inner.mod_floor(&y.inner))
277    }
278
279    /// Performs the `+` operation.
280    #[inline]
281    #[must_use]
282    pub fn add(x: &Self, y: &Self) -> Self {
283        Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
284    }
285
286    /// Performs the `-` operation.
287    #[inline]
288    #[must_use]
289    pub fn sub(x: &Self, y: &Self) -> Self {
290        Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
291    }
292
293    /// Performs the `*` operation.
294    #[inline]
295    #[must_use]
296    pub fn mul(x: &Self, y: &Self) -> Self {
297        Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
298    }
299
300    /// Performs the `/` operation.
301    #[inline]
302    #[must_use]
303    pub fn div(x: &Self, y: &Self) -> Self {
304        Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
305    }
306
307    /// Performs the `%` operation.
308    #[inline]
309    #[must_use]
310    pub fn rem(x: &Self, y: &Self) -> Self {
311        Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
312    }
313
314    /// Performs the `&` operation.
315    #[inline]
316    #[must_use]
317    pub fn bitand(x: &Self, y: &Self) -> Self {
318        Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
319    }
320
321    /// Performs the `|` operation.
322    #[inline]
323    #[must_use]
324    pub fn bitor(x: &Self, y: &Self) -> Self {
325        Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
326    }
327
328    /// Performs the `^` operation.
329    #[inline]
330    #[must_use]
331    pub fn bitxor(x: &Self, y: &Self) -> Self {
332        Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
333    }
334
335    /// Performs the unary `-` operation.
336    #[inline]
337    #[must_use]
338    pub fn neg(x: &Self) -> Self {
339        Self::new(x.as_inner().neg())
340    }
341
342    /// Performs the unary `!` operation.
343    #[inline]
344    #[must_use]
345    pub fn not(x: &Self) -> Self {
346        Self::new(!x.as_inner())
347    }
348
349    /// Returns a reference to the raw inner value.
350    #[inline]
351    #[must_use]
352    pub fn as_inner(&self) -> &RawBigInt {
353        &self.inner
354    }
355
356    /// Consumes the [`JsBigInt`], returning a pointer to [`RawBigInt`].
357    ///
358    /// To avoid a memory leak the pointer must be converted back to a `JsBigInt` using
359    /// [`JsBigInt::from_raw`].
360    #[inline]
361    #[must_use]
362    #[allow(unused, reason = "only used in nan-boxed implementation of JsValue")]
363    pub(crate) fn into_raw(self) -> NonNull<RawBigInt> {
364        // SAFETY: `Rc::into_raw` must always return a non-null pointer.
365        unsafe { NonNull::new_unchecked(Rc::into_raw(self.inner).cast_mut()) }
366    }
367
368    /// Constructs a `JsBigInt` from a pointer to [`RawBigInt`].
369    ///
370    /// The raw pointer must have been previously returned by a call to
371    /// [`JsBigInt::into_raw`].
372    ///
373    /// # Safety
374    ///
375    /// This function is unsafe because improper use may lead to memory unsafety,
376    /// even if the returned `JsBigInt` is never accessed.
377    #[inline]
378    #[must_use]
379    #[allow(unused, reason = "only used in nan-boxed implementation of JsValue")]
380    pub(crate) unsafe fn from_raw(ptr: *const RawBigInt) -> Self {
381        Self {
382            // SAFETY: the validity of `ptr` is guaranteed by the caller.
383            inner: unsafe { Rc::from_raw(ptr) },
384        }
385    }
386}
387
388impl Display for JsBigInt {
389    #[inline]
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        Display::fmt(&self.inner, f)
392    }
393}
394
395impl From<RawBigInt> for JsBigInt {
396    #[inline]
397    fn from(value: RawBigInt) -> Self {
398        Self {
399            inner: Rc::new(value),
400        }
401    }
402}
403
404impl From<Box<RawBigInt>> for JsBigInt {
405    #[inline]
406    fn from(value: Box<RawBigInt>) -> Self {
407        Self {
408            inner: value.into(),
409        }
410    }
411}
412
413impl From<i8> for JsBigInt {
414    #[inline]
415    fn from(value: i8) -> Self {
416        Self {
417            inner: Rc::new(RawBigInt::from(value)),
418        }
419    }
420}
421
422impl From<u8> for JsBigInt {
423    #[inline]
424    fn from(value: u8) -> Self {
425        Self {
426            inner: Rc::new(RawBigInt::from(value)),
427        }
428    }
429}
430
431impl From<i16> for JsBigInt {
432    #[inline]
433    fn from(value: i16) -> Self {
434        Self {
435            inner: Rc::new(RawBigInt::from(value)),
436        }
437    }
438}
439
440impl From<u16> for JsBigInt {
441    #[inline]
442    fn from(value: u16) -> Self {
443        Self {
444            inner: Rc::new(RawBigInt::from(value)),
445        }
446    }
447}
448
449impl From<i32> for JsBigInt {
450    #[inline]
451    fn from(value: i32) -> Self {
452        Self {
453            inner: Rc::new(RawBigInt::from(value)),
454        }
455    }
456}
457
458impl From<u32> for JsBigInt {
459    #[inline]
460    fn from(value: u32) -> Self {
461        Self {
462            inner: Rc::new(RawBigInt::from(value)),
463        }
464    }
465}
466
467impl From<i64> for JsBigInt {
468    #[inline]
469    fn from(value: i64) -> Self {
470        Self {
471            inner: Rc::new(RawBigInt::from(value)),
472        }
473    }
474}
475
476impl From<u64> for JsBigInt {
477    #[inline]
478    fn from(value: u64) -> Self {
479        Self {
480            inner: Rc::new(RawBigInt::from(value)),
481        }
482    }
483}
484
485impl From<i128> for JsBigInt {
486    #[inline]
487    fn from(value: i128) -> Self {
488        Self {
489            inner: Rc::new(RawBigInt::from(value)),
490        }
491    }
492}
493
494impl From<u128> for JsBigInt {
495    #[inline]
496    fn from(value: u128) -> Self {
497        Self {
498            inner: Rc::new(RawBigInt::from(value)),
499        }
500    }
501}
502
503impl From<isize> for JsBigInt {
504    #[inline]
505    fn from(value: isize) -> Self {
506        Self {
507            inner: Rc::new(RawBigInt::from(value)),
508        }
509    }
510}
511
512impl From<usize> for JsBigInt {
513    #[inline]
514    fn from(value: usize) -> Self {
515        Self {
516            inner: Rc::new(RawBigInt::from(value)),
517        }
518    }
519}
520
521/// The error indicates that the conversion from [`f64`] to [`JsBigInt`] failed.
522#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
523pub struct TryFromF64Error;
524
525impl Display for TryFromF64Error {
526    #[inline]
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        write!(f, "Could not convert f64 value to a BigInt type")
529    }
530}
531
532impl TryFrom<f64> for JsBigInt {
533    type Error = TryFromF64Error;
534
535    #[inline]
536    fn try_from(n: f64) -> Result<Self, Self::Error> {
537        // If the truncated version of the number is not the
538        // same as the non-truncated version then the floating-point
539        // number contains a fractional part.
540        if !Number::equal(n.trunc(), n) {
541            return Err(TryFromF64Error);
542        }
543        RawBigInt::from_f64(n).map_or(Err(TryFromF64Error), |bigint| Ok(Self::new(bigint)))
544    }
545}
546
547impl PartialEq<i32> for JsBigInt {
548    #[inline]
549    fn eq(&self, other: &i32) -> bool {
550        self.inner.as_ref() == &RawBigInt::from(*other)
551    }
552}
553
554impl PartialEq<JsBigInt> for i32 {
555    #[inline]
556    fn eq(&self, other: &JsBigInt) -> bool {
557        &RawBigInt::from(*self) == other.inner.as_ref()
558    }
559}
560
561impl PartialEq<f64> for JsBigInt {
562    #[inline]
563    fn eq(&self, other: &f64) -> bool {
564        other.fract().is_zero()
565            && RawBigInt::from_f64(*other).is_some_and(|bigint| self.inner.as_ref() == &bigint)
566    }
567}
568
569impl PartialEq<JsBigInt> for f64 {
570    #[inline]
571    fn eq(&self, other: &JsBigInt) -> bool {
572        self.fract().is_zero()
573            && RawBigInt::from_f64(*self).is_some_and(|bigint| other.inner.as_ref() == &bigint)
574    }
575}