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