boa_engine/
bigint.rs

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