1use super::primitive::is_ecmascript_whitespace;
14use std::borrow::Cow;
15use std::cmp::Ordering;
16use std::fmt;
17use std::rc::Rc;
18use std::str::FromStr;
19
20use num_bigint::{BigInt, BigUint, Sign};
21use num_traits::{FromPrimitive, One, ToPrimitive, Zero};
22
23pub const MAX_BIGINT_BITS: u64 = 1024 * 1024;
31
32pub const BIGINT_LIMB_BITS: u64 = 64;
34
35const MAX_BIGINT_LIMBS: u64 = MAX_BIGINT_BITS / BIGINT_LIMB_BITS;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum BigIntError {
40 InvalidSyntax,
42 InvalidRadix(u32),
44 DivisionByZero,
46 NegativeExponent,
48 BigIntTooLarge,
50 AllocationTooLarge,
53 ShiftTooLarge,
55}
56
57impl fmt::Display for BigIntError {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Self::InvalidSyntax => formatter.write_str("invalid BigInt syntax"),
61 Self::InvalidRadix(radix) => {
62 write!(
63 formatter,
64 "invalid BigInt radix {radix}; expected 2 through 36"
65 )
66 }
67 Self::DivisionByZero => formatter.write_str("BigInt division by zero"),
68 Self::NegativeExponent => formatter.write_str("BigInt negative exponent"),
69 Self::BigIntTooLarge => formatter.write_str("BigInt is too large"),
70 Self::AllocationTooLarge => formatter.write_str("BigInt is too large to allocate"),
71 Self::ShiftTooLarge => formatter.write_str("BigInt shift is too large"),
72 }
73 }
74}
75
76impl std::error::Error for BigIntError {}
77
78#[derive(Clone, Eq, Hash, PartialEq)]
79enum BigIntRepr {
80 Short(i64),
81 Heap(Rc<BigInt>),
82}
83
84#[derive(Clone, Eq, Hash, PartialEq)]
89pub struct JsBigInt(BigIntRepr);
90
91impl JsBigInt {
92 #[must_use]
96 pub fn same_representation(&self, other: &Self) -> bool {
97 match (&self.0, &other.0) {
98 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left == right,
99 (BigIntRepr::Heap(left), BigIntRepr::Heap(right)) => Rc::ptr_eq(left, right),
100 (BigIntRepr::Short(_), BigIntRepr::Heap(_))
101 | (BigIntRepr::Heap(_), BigIntRepr::Short(_)) => false,
102 }
103 }
104
105 #[must_use]
107 pub const fn zero() -> Self {
108 Self(BigIntRepr::Short(0))
109 }
110
111 #[must_use]
113 pub const fn one() -> Self {
114 Self(BigIntRepr::Short(1))
115 }
116
117 pub fn from_bigint(value: BigInt) -> Result<Self, BigIntError> {
124 if signed_limb_len(&value) > MAX_BIGINT_LIMBS {
125 return Err(BigIntError::BigIntTooLarge);
126 }
127 Ok(Self::normalize(value))
128 }
129
130 pub(crate) fn from_decimal_unchecked(text: &str) -> Option<Self> {
134 BigInt::parse_bytes(text.as_bytes(), 10).map(Self::normalize)
135 }
136
137 #[must_use]
143 pub fn from_integral_f64(value: f64) -> Option<Self> {
144 if !value.is_finite() || value.fract() != 0.0 {
145 return None;
146 }
147 BigInt::from_f64(value).map(Self::normalize)
148 }
149
150 pub fn parse_js_string(source: &str) -> Result<Self, BigIntError> {
162 let source = source.trim_matches(|character: char| {
163 u16::try_from(character).is_ok_and(is_ecmascript_whitespace)
164 });
165 if source.is_empty() {
166 return Ok(Self::zero());
167 }
168
169 let bytes = source.as_bytes();
170 match bytes[0] {
171 b'+' | b'-' => {
172 let negative = bytes[0] == b'-';
173 let digits = &source[1..];
174 parse_digits(digits, 10, negative)
176 }
177 _ if bytes.len() >= 2 && bytes[0] == b'0' => {
178 let radix = match bytes[1] {
179 b'b' | b'B' => Some(2),
180 b'o' | b'O' => Some(8),
181 b'x' | b'X' => Some(16),
182 _ => None,
183 };
184 if let Some(radix) = radix {
185 parse_digits(&source[2..], radix, false)
186 } else {
187 parse_digits(source, 10, false)
188 }
189 }
190 _ => parse_digits(source, 10, false),
191 }
192 }
193
194 pub fn parse_radix(source: &str, radix: u32) -> Result<Self, BigIntError> {
204 validate_radix(radix)?;
205 let (negative, digits) = match source.as_bytes().first().copied() {
206 Some(b'-') => (true, &source[1..]),
207 Some(b'+') => (false, &source[1..]),
208 _ => (false, source),
209 };
210 parse_digits(digits, radix, negative)
211 }
212
213 pub fn to_string_radix(&self, radix: u32) -> Result<String, BigIntError> {
219 validate_radix(radix)?;
220 Ok(match &self.0 {
221 BigIntRepr::Short(value) => short_to_string_radix(*value, radix),
222 BigIntRepr::Heap(value) => value.to_str_radix(radix),
223 })
224 }
225
226 #[must_use]
228 pub const fn is_short(&self) -> bool {
229 matches!(self.0, BigIntRepr::Short(_))
230 }
231
232 #[must_use]
234 pub const fn as_i64(&self) -> Option<i64> {
235 match self.0 {
236 BigIntRepr::Short(value) => Some(value),
237 BigIntRepr::Heap(_) => None,
238 }
239 }
240
241 #[must_use]
243 pub fn to_bigint(&self) -> BigInt {
244 match &self.0 {
245 BigIntRepr::Short(value) => BigInt::from(*value),
246 BigIntRepr::Heap(value) => value.as_ref().clone(),
247 }
248 }
249
250 #[must_use]
258 pub fn to_f64(&self) -> f64 {
259 match &self.0 {
260 BigIntRepr::Short(value) => *value as f64,
261 BigIntRepr::Heap(value) => value
262 .to_f64()
263 .expect("an arbitrary-precision integer always has an f64 image"),
264 }
265 }
266
267 #[must_use]
269 pub fn cmp_f64(&self, number: f64) -> Option<Ordering> {
270 const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
271 if number.is_nan() {
272 return None;
273 }
274 if (-TWO_POW_63..TWO_POW_63).contains(&number) {
275 let ordering = match &self.0 {
276 BigIntRepr::Short(value) => value.cmp(&(number.trunc() as i64)),
277 BigIntRepr::Heap(value) if value.sign() == Sign::Minus => Ordering::Less,
279 BigIntRepr::Heap(_) => Ordering::Greater,
280 };
281 return Some(ordering.then(0.0.partial_cmp(&number.fract())?));
282 }
283 match &self.0 {
286 BigIntRepr::Heap(value)
287 if number.is_finite() && (value.sign() == Sign::Minus) == (number < 0.0) =>
288 {
289 let number_bits = ((number.to_bits() >> 52) & 0x7ff) - 1022;
290 let magnitude = value.bits().cmp(&number_bits).then_with(|| {
291 let number = BigUint::from_f64(number.abs()).expect("finite Number");
292 value.magnitude().cmp(&number)
293 });
294 Some(if number < 0.0 {
295 magnitude.reverse()
296 } else {
297 magnitude
298 })
299 }
300 _ => Some(if number > 0.0 {
301 Ordering::Less
302 } else {
303 Ordering::Greater
304 }),
305 }
306 }
307
308 #[must_use]
310 pub fn magnitude_bits(&self) -> u64 {
311 match &self.0 {
312 BigIntRepr::Short(value) => {
313 let magnitude = value.unsigned_abs();
314 u64::from(u64::BITS - magnitude.leading_zeros())
315 }
316 BigIntRepr::Heap(value) => value.bits(),
317 }
318 }
319
320 #[must_use]
322 pub fn signed_limb_len(&self) -> u64 {
323 match &self.0 {
324 BigIntRepr::Short(_) => 1,
325 BigIntRepr::Heap(value) => signed_limb_len(value),
326 }
327 }
328
329 pub fn exceeds_allocation_limit(&self) -> bool {
330 self.signed_limb_len() > MAX_BIGINT_LIMBS
331 }
332
333 #[must_use]
335 pub fn is_zero(&self) -> bool {
336 match &self.0 {
337 BigIntRepr::Short(value) => *value == 0,
338 BigIntRepr::Heap(value) => value.is_zero(),
339 }
340 }
341
342 #[must_use]
344 pub fn is_negative(&self) -> bool {
345 match &self.0 {
346 BigIntRepr::Short(value) => *value < 0,
347 BigIntRepr::Heap(value) => value.sign() == Sign::Minus,
348 }
349 }
350
351 pub fn add(&self, rhs: &Self) -> Result<Self, BigIntError> {
358 let short_result = match (&self.0, &rhs.0) {
359 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_add(*right),
360 _ => None,
361 };
362 if let Some(result) = short_result {
363 return Ok(Self::from(result));
364 }
365 self.ensure_operands_allocatable(rhs)?;
366 self.checked_extending_binary(rhs, |left, right| left + right)
367 }
368
369 pub fn sub(&self, rhs: &Self) -> Result<Self, BigIntError> {
377 let short_result = match (&self.0, &rhs.0) {
378 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_sub(*right),
379 _ => None,
380 };
381 if let Some(result) = short_result {
382 return Ok(Self::from(result));
383 }
384 self.ensure_operands_allocatable(rhs)?;
385 self.checked_extending_binary(rhs, |left, right| left - right)
386 }
387
388 pub fn update_decrement(&self) -> Result<Self, BigIntError> {
397 if let BigIntRepr::Short(value) = self.0
398 && value != i64::MIN
399 {
400 return Ok(Self::from(value - 1));
401 }
402 self.add(&Self::from(i64::from(u32::MAX)))
403 }
404
405 pub fn mul(&self, rhs: &Self) -> Result<Self, BigIntError> {
413 let short_result = match (&self.0, &rhs.0) {
414 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_mul(*right),
415 _ => None,
416 };
417 if let Some(result) = short_result {
418 return Ok(Self::from(result));
419 }
420 self.ensure_operands_allocatable(rhs)?;
421 let allocated_limbs = self
422 .signed_limb_len()
423 .checked_add(rhs.signed_limb_len())
424 .ok_or(BigIntError::AllocationTooLarge)?;
425 if allocated_limbs > MAX_BIGINT_LIMBS {
426 return Err(BigIntError::AllocationTooLarge);
427 }
428 self.checked_binary(rhs, |left, right| left * right)
429 }
430
431 pub fn div(&self, rhs: &Self) -> Result<Self, BigIntError> {
437 if rhs.is_zero() {
438 return Err(BigIntError::DivisionByZero);
439 }
440 let short_result = match (&self.0, &rhs.0) {
441 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_div(*right),
442 _ => None,
443 };
444 if let Some(result) = short_result {
445 return Ok(Self::from(result));
446 }
447 if self
448 .signed_limb_len()
449 .checked_add(2)
450 .is_none_or(|allocated_limbs| allocated_limbs > MAX_BIGINT_LIMBS)
451 {
452 return Err(BigIntError::AllocationTooLarge);
453 }
454 self.checked_binary(rhs, |left, right| left / right)
457 }
458
459 pub fn rem(&self, rhs: &Self) -> Result<Self, BigIntError> {
465 if rhs.is_zero() {
466 return Err(BigIntError::DivisionByZero);
467 }
468 if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
469 if *left == i64::MIN && *right == -1 {
472 return Ok(Self::zero());
473 }
474 return Ok(Self::from(left % right));
475 }
476 if self
477 .signed_limb_len()
478 .checked_add(2)
479 .is_none_or(|allocated_limbs| allocated_limbs > MAX_BIGINT_LIMBS)
480 {
481 return Err(BigIntError::AllocationTooLarge);
482 }
483 self.checked_binary(rhs, |left, right| left % right)
484 }
485
486 pub fn pow(&self, exponent: &Self) -> Result<Self, BigIntError> {
499 if exponent.is_negative() {
500 return Err(BigIntError::NegativeExponent);
501 }
502 if exponent.is_zero() {
503 return Ok(Self::one());
504 }
505 if exponent.is_one() {
506 if self.exceeds_allocation_limit() {
507 return Err(BigIntError::AllocationTooLarge);
508 }
509 return Ok(self.clone());
510 }
511 if self.is_zero() || self.is_one() {
512 return Ok(self.clone());
513 }
514 if self.is_negative_one() {
515 return Ok(if exponent.is_odd() {
516 Self::from(-1)
517 } else {
518 Self::one()
519 });
520 }
521
522 let exponent = exponent
523 .to_u32()
524 .filter(|value| *value <= i32::MAX as u32)
525 .ok_or(BigIntError::BigIntTooLarge)?;
526
527 if let Some(base) = self.as_i64() {
531 let magnitude = base.unsigned_abs();
532 if magnitude.is_power_of_two() {
533 let base_shift = u64::from(magnitude.trailing_zeros());
534 let result_shift = u64::from(exponent) * base_shift;
535 if result_shift > MAX_BIGINT_BITS {
536 return Err(BigIntError::BigIntTooLarge);
537 }
538 let mut result = BigInt::one()
539 << usize::try_from(result_shift)
540 .expect("bounded QuickJS BigInt power shift fits usize");
541 if base.is_negative() && exponent & 1 != 0 {
542 result = -result;
543 }
544 return Self::from_bigint(result).map_err(|error| match error {
545 BigIntError::BigIntTooLarge => BigIntError::AllocationTooLarge,
546 error => error,
547 });
548 }
549 }
550
551 if self.exceeds_allocation_limit() {
556 return Err(BigIntError::AllocationTooLarge);
557 }
558 let highest_bit = u32::BITS - exponent.leading_zeros();
559 let mut result = self.clone();
560 for bit in (0..highest_bit - 1).rev() {
561 result = result.mul(&result)?;
562 if exponent & (1 << bit) != 0 {
563 result = result.mul(self)?;
564 }
565 }
566 Ok(result)
567 }
568
569 pub fn neg(&self) -> Result<Self, BigIntError> {
576 let short_result = match &self.0 {
577 BigIntRepr::Short(value) => value.checked_neg(),
578 BigIntRepr::Heap(_) => None,
579 };
580 if let Some(result) = short_result {
581 return Ok(Self::from(result));
582 }
583 if self.exceeds_allocation_limit() {
584 return Err(BigIntError::AllocationTooLarge);
585 }
586 let result = -self.as_bigint().as_ref();
587 if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
588 return Err(BigIntError::AllocationTooLarge);
589 }
590 Ok(Self::normalize(result))
591 }
592
593 pub fn bit_and(&self, rhs: &Self) -> Result<Self, BigIntError> {
600 if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
601 return Ok(Self::from(left & right));
602 }
603 self.ensure_operands_allocatable(rhs)?;
604 Ok(self.unchecked_binary(rhs, |left, right| left & right))
605 }
606
607 pub fn bit_or(&self, rhs: &Self) -> Result<Self, BigIntError> {
614 if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
615 return Ok(Self::from(left | right));
616 }
617 self.ensure_operands_allocatable(rhs)?;
618 Ok(self.unchecked_binary(rhs, |left, right| left | right))
619 }
620
621 pub fn bit_xor(&self, rhs: &Self) -> Result<Self, BigIntError> {
628 if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
629 return Ok(Self::from(left ^ right));
630 }
631 self.ensure_operands_allocatable(rhs)?;
632 Ok(self.unchecked_binary(rhs, |left, right| left ^ right))
633 }
634
635 pub fn bit_not(&self) -> Result<Self, BigIntError> {
642 if self.exceeds_allocation_limit() {
643 return Err(BigIntError::AllocationTooLarge);
644 }
645 Ok(match &self.0 {
646 BigIntRepr::Short(value) => Self::from(!value),
647 BigIntRepr::Heap(value) => Self::normalize(!value.as_ref()),
648 })
649 }
650
651 pub fn shl(&self, count: &Self) -> Result<Self, BigIntError> {
658 self.shift(count, true)
659 }
660
661 pub fn shr(&self, count: &Self) -> Result<Self, BigIntError> {
668 self.shift(count, false)
669 }
670
671 pub fn as_int_n(&self, bits: u64) -> Result<Self, BigIntError> {
680 self.as_n(bits, true)
681 }
682
683 pub fn as_uint_n(&self, bits: u64) -> Result<Self, BigIntError> {
696 self.as_n(bits, false)
697 }
698
699 fn normalize(value: BigInt) -> Self {
700 if let Some(value) = value.to_i64() {
701 Self(BigIntRepr::Short(value))
702 } else {
703 Self(BigIntRepr::Heap(Rc::new(value)))
704 }
705 }
706
707 fn as_bigint(&self) -> Cow<'_, BigInt> {
708 match &self.0 {
709 BigIntRepr::Short(value) => Cow::Owned(BigInt::from(*value)),
710 BigIntRepr::Heap(value) => Cow::Borrowed(value.as_ref()),
711 }
712 }
713
714 fn checked_binary(
715 &self,
716 rhs: &Self,
717 operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
718 ) -> Result<Self, BigIntError> {
719 let left = self.as_bigint();
720 let right = rhs.as_bigint();
721 Self::from_bigint(operation(left.as_ref(), right.as_ref())).map_err(|error| match error {
722 BigIntError::BigIntTooLarge => BigIntError::AllocationTooLarge,
723 error => error,
724 })
725 }
726
727 fn checked_extending_binary(
728 &self,
729 rhs: &Self,
730 operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
731 ) -> Result<Self, BigIntError> {
732 let left = self.as_bigint();
733 let right = rhs.as_bigint();
734 let result = operation(left.as_ref(), right.as_ref());
735 if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
736 return Err(BigIntError::AllocationTooLarge);
737 }
738 Ok(Self::normalize(result))
739 }
740
741 fn ensure_operands_allocatable(&self, rhs: &Self) -> Result<(), BigIntError> {
742 if self.exceeds_allocation_limit() || rhs.exceeds_allocation_limit() {
743 Err(BigIntError::AllocationTooLarge)
744 } else {
745 Ok(())
746 }
747 }
748
749 fn unchecked_binary(
750 &self,
751 rhs: &Self,
752 operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
753 ) -> Self {
754 let left = self.as_bigint();
755 let right = rhs.as_bigint();
756 let result = operation(left.as_ref(), right.as_ref());
757 debug_assert!(signed_limb_len(&result) <= MAX_BIGINT_LIMBS);
758 Self::normalize(result)
759 }
760
761 fn is_one(&self) -> bool {
762 matches!(self.0, BigIntRepr::Short(1))
763 }
764
765 fn is_negative_one(&self) -> bool {
766 matches!(self.0, BigIntRepr::Short(-1))
767 }
768
769 fn is_odd(&self) -> bool {
770 match &self.0 {
771 BigIntRepr::Short(value) => value & 1 != 0,
772 BigIntRepr::Heap(value) => value.magnitude().bit(0),
773 }
774 }
775
776 fn to_u32(&self) -> Option<u32> {
777 match &self.0 {
778 BigIntRepr::Short(value) => u32::try_from(*value).ok(),
779 BigIntRepr::Heap(value) => value.to_u32(),
780 }
781 }
782
783 fn shift(&self, count: &Self, operator_shifts_left: bool) -> Result<Self, BigIntError> {
784 let shifts_left = operator_shifts_left != count.is_negative();
785 let count = count
786 .magnitude_to_u64()
787 .unwrap_or(u64::MAX)
788 .min(i32::MAX as u64);
789 if shifts_left {
790 if self.is_zero() {
792 return Ok(Self::zero());
793 }
794 let allocated_limbs = self
795 .signed_limb_len()
796 .checked_add(count / BIGINT_LIMB_BITS)
797 .ok_or(BigIntError::ShiftTooLarge)?;
798 if allocated_limbs > MAX_BIGINT_LIMBS {
799 return Err(BigIntError::ShiftTooLarge);
800 }
801 let count = usize::try_from(count).map_err(|_| BigIntError::ShiftTooLarge)?;
802 let result = self.as_bigint().as_ref() << count;
803 if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
804 return Err(BigIntError::ShiftTooLarge);
805 }
806 Ok(Self::normalize(result))
807 } else {
808 let limb_offset = count / BIGINT_LIMB_BITS;
809 if limb_offset >= self.signed_limb_len() {
810 return Ok(self.right_shift_saturation());
811 }
812 if self.signed_limb_len() - limb_offset > MAX_BIGINT_LIMBS {
813 return Err(BigIntError::ShiftTooLarge);
814 }
815 let count = usize::try_from(count).expect("bounded BigInt shift fits usize");
816 Ok(Self::normalize(self.as_bigint().as_ref() >> count))
817 }
818 }
819
820 fn right_shift_saturation(&self) -> Self {
821 if self.is_negative() {
822 Self::from(-1)
823 } else {
824 Self::zero()
825 }
826 }
827
828 fn magnitude_to_u64(&self) -> Option<u64> {
829 match &self.0 {
830 BigIntRepr::Short(value) => Some(value.unsigned_abs()),
831 BigIntRepr::Heap(value) => value.magnitude().to_u64(),
832 }
833 }
834
835 fn as_n(&self, bits: u64, signed: bool) -> Result<Self, BigIntError> {
836 if bits == 0 {
837 return Ok(Self::zero());
838 }
839
840 let storage_bits = self.signed_limb_len() * BIGINT_LIMB_BITS;
843 if bits >= storage_bits {
844 return Ok(self.clone());
845 }
846
847 let allocated_limbs = bits
848 .checked_add(BIGINT_LIMB_BITS - 1)
849 .ok_or(BigIntError::AllocationTooLarge)?
850 / BIGINT_LIMB_BITS;
851 if allocated_limbs > MAX_BIGINT_LIMBS {
852 return Err(BigIntError::AllocationTooLarge);
853 }
854
855 let bits_usize = usize::try_from(bits).expect("bounded BigInt bit width fits usize");
857 let modulus = BigInt::one() << bits_usize;
858 let mut truncated = self.as_bigint().as_ref() % &modulus;
859 if truncated.sign() == Sign::Minus {
860 truncated += &modulus;
861 }
862
863 let interpret_as_signed = signed || bits & (BIGINT_LIMB_BITS - 1) == 0;
867 if interpret_as_signed && truncated.bit(bits - 1) {
868 truncated -= modulus;
869 }
870 Ok(Self::normalize(truncated))
871 }
872}
873
874impl Default for JsBigInt {
875 fn default() -> Self {
876 Self::zero()
877 }
878}
879
880impl From<i64> for JsBigInt {
881 fn from(value: i64) -> Self {
882 Self(BigIntRepr::Short(value))
883 }
884}
885
886impl From<i32> for JsBigInt {
887 fn from(value: i32) -> Self {
888 Self::from(i64::from(value))
889 }
890}
891
892impl From<u32> for JsBigInt {
893 fn from(value: u32) -> Self {
894 Self::from(i64::from(value))
895 }
896}
897
898impl From<u64> for JsBigInt {
899 fn from(value: u64) -> Self {
900 if let Ok(value) = i64::try_from(value) {
901 Self::from(value)
902 } else {
903 Self::normalize(BigInt::from(value))
904 }
905 }
906}
907
908impl From<i128> for JsBigInt {
909 fn from(value: i128) -> Self {
910 Self::normalize(BigInt::from(value))
911 }
912}
913
914impl From<u128> for JsBigInt {
915 fn from(value: u128) -> Self {
916 Self::normalize(BigInt::from(value))
917 }
918}
919
920impl TryFrom<BigInt> for JsBigInt {
921 type Error = BigIntError;
922
923 fn try_from(value: BigInt) -> Result<Self, Self::Error> {
924 Self::from_bigint(value)
925 }
926}
927
928impl FromStr for JsBigInt {
929 type Err = BigIntError;
930
931 fn from_str(source: &str) -> Result<Self, Self::Err> {
932 Self::parse_js_string(source)
933 }
934}
935
936impl Ord for JsBigInt {
937 fn cmp(&self, other: &Self) -> Ordering {
938 match (&self.0, &other.0) {
939 (BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.cmp(right),
940 (BigIntRepr::Heap(left), BigIntRepr::Heap(right)) => left.cmp(right),
941 (BigIntRepr::Short(_), BigIntRepr::Heap(heap)) => match heap.sign() {
943 Sign::Minus => Ordering::Greater,
944 _ => Ordering::Less,
945 },
946 (BigIntRepr::Heap(heap), BigIntRepr::Short(_)) => match heap.sign() {
947 Sign::Minus => Ordering::Less,
948 _ => Ordering::Greater,
949 },
950 }
951 }
952}
953
954impl PartialOrd for JsBigInt {
955 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
956 Some(self.cmp(other))
957 }
958}
959
960impl fmt::Display for JsBigInt {
961 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
962 match &self.0 {
963 BigIntRepr::Short(value) => value.fmt(formatter),
964 BigIntRepr::Heap(value) => value.fmt(formatter),
965 }
966 }
967}
968
969impl fmt::Debug for JsBigInt {
970 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
971 match &self.0 {
972 BigIntRepr::Short(value) => formatter.debug_tuple("ShortBigInt").field(value).finish(),
973 BigIntRepr::Heap(value) => formatter.debug_tuple("BigInt").field(value).finish(),
974 }
975 }
976}
977
978fn validate_radix(radix: u32) -> Result<(), BigIntError> {
979 if (2..=36).contains(&radix) {
980 Ok(())
981 } else {
982 Err(BigIntError::InvalidRadix(radix))
983 }
984}
985
986fn parse_digits(digits: &str, radix: u32, negative: bool) -> Result<JsBigInt, BigIntError> {
987 validate_radix(radix)?;
988 if digits.is_empty()
989 || !digits
990 .as_bytes()
991 .iter()
992 .all(|byte| is_radix_digit(*byte, radix))
993 {
994 return Err(BigIntError::InvalidSyntax);
995 }
996
997 let significant_digits = digits.trim_start_matches('0');
1000 if significant_digits.is_empty() {
1001 return Ok(JsBigInt::zero());
1002 }
1003 let digit_count = u64::try_from(significant_digits.len()).unwrap_or(u64::MAX);
1004 if digit_count > MAX_BIGINT_BITS {
1005 return Err(BigIntError::AllocationTooLarge);
1006 }
1007
1008 let log2_radix = u64::from(u32::BITS - (radix - 1).leading_zeros());
1015 let estimated_bits = if radix == 10 {
1016 (digit_count * 27).div_ceil(8)
1017 } else {
1018 digit_count * log2_radix
1019 };
1020 let estimated_limbs = estimated_bits / BIGINT_LIMB_BITS + 1;
1021 if estimated_limbs > MAX_BIGINT_LIMBS {
1022 return Err(BigIntError::AllocationTooLarge);
1023 }
1024
1025 let magnitude = BigUint::parse_bytes(significant_digits.as_bytes(), radix)
1026 .ok_or(BigIntError::InvalidSyntax)?;
1027 let sign = if negative && !magnitude.is_zero() {
1028 Sign::Minus
1029 } else if magnitude.is_zero() {
1030 Sign::NoSign
1031 } else {
1032 Sign::Plus
1033 };
1034 JsBigInt::from_bigint(BigInt::from_biguint(sign, magnitude))
1035}
1036
1037fn is_radix_digit(byte: u8, radix: u32) -> bool {
1038 let value = match byte {
1039 b'0'..=b'9' => u32::from(byte - b'0'),
1040 b'a'..=b'z' => u32::from(byte - b'a') + 10,
1041 b'A'..=b'Z' => u32::from(byte - b'A') + 10,
1042 _ => return false,
1043 };
1044 value < radix
1045}
1046
1047fn short_to_string_radix(value: i64, radix: u32) -> String {
1048 const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
1049
1050 if value == 0 {
1051 return "0".to_owned();
1052 }
1053
1054 let negative = value < 0;
1055 let mut magnitude = value.unsigned_abs();
1056 let radix = u64::from(radix);
1057 let mut reversed = Vec::with_capacity(65);
1058 while magnitude != 0 {
1059 let digit = usize::try_from(magnitude % radix).expect("radix digit fits usize");
1060 reversed.push(DIGITS[digit]);
1061 magnitude /= radix;
1062 }
1063 if negative {
1064 reversed.push(b'-');
1065 }
1066 reversed.reverse();
1067 String::from_utf8(reversed).expect("BigInt formatting only emits ASCII")
1068}
1069
1070fn signed_limb_len(value: &BigInt) -> u64 {
1071 let signed_bits = match value.sign() {
1072 Sign::NoSign => 1,
1073 Sign::Plus => value.bits() + 1,
1074 Sign::Minus => {
1077 let magnitude = value.magnitude();
1078 let bits = magnitude.bits();
1079 if magnitude.trailing_zeros() == Some(bits - 1) {
1080 bits
1081 } else {
1082 bits + 1
1083 }
1084 }
1085 };
1086 signed_bits.div_ceil(BIGINT_LIMB_BITS).max(1)
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use super::{BIGINT_LIMB_BITS, BigIntError, JsBigInt, MAX_BIGINT_BITS, MAX_BIGINT_LIMBS};
1092
1093 fn bigint(source: &str) -> JsBigInt {
1094 JsBigInt::parse_js_string(source).unwrap()
1095 }
1096
1097 #[test]
1098 fn normalizes_short_values_and_promotes_overflow() {
1099 let max = JsBigInt::from(i64::MAX);
1100 let min = JsBigInt::from(i64::MIN);
1101 assert!(max.is_short());
1102 assert!(min.is_short());
1103
1104 let above_max = max.add(&JsBigInt::one()).unwrap();
1105 let below_min = min.sub(&JsBigInt::one()).unwrap();
1106 assert!(!above_max.is_short());
1107 assert!(!below_min.is_short());
1108 assert!(max < above_max && min < above_max && below_min < min && below_min < max);
1109 assert!(above_max > max && below_min < above_max && max > below_min);
1110 assert_eq!(above_max.to_string(), "9223372036854775808");
1111 assert_eq!(below_min.to_string(), "-9223372036854775809");
1112
1113 assert_eq!(above_max.sub(&JsBigInt::one()).unwrap(), max);
1114 assert_eq!(below_min.add(&JsBigInt::one()).unwrap(), min);
1115 assert!(above_max.sub(&JsBigInt::one()).unwrap().is_short());
1116 }
1117
1118 #[test]
1119 fn update_decrement_preserves_pinned_unsigned_opcode_quirk() {
1120 assert_eq!(
1121 JsBigInt::from(4).update_decrement().unwrap(),
1122 JsBigInt::from(3)
1123 );
1124 assert_eq!(
1125 JsBigInt::from(i64::MIN).update_decrement().unwrap(),
1126 bigint("-9223372032559808513")
1127 );
1128 assert_eq!(
1129 bigint("9223372036854775808").update_decrement().unwrap(),
1130 bigint("9223372041149743103")
1131 );
1132 assert_eq!(
1133 bigint("-9223372036854775809").update_decrement().unwrap(),
1134 bigint("-9223372032559808514")
1135 );
1136 assert_eq!(
1137 bigint("9223372036854775808").sub(&JsBigInt::one()).unwrap(),
1138 JsBigInt::from(i64::MAX)
1139 );
1140 }
1141
1142 #[test]
1143 fn signed_limb_width_matches_quickjs_twos_complement_normalization() {
1144 assert_eq!(JsBigInt::from(i64::MAX).signed_limb_len(), 1);
1145 assert_eq!(JsBigInt::from(i64::MIN).signed_limb_len(), 1);
1146 assert_eq!(bigint("9223372036854775808").signed_limb_len(), 2);
1147 assert_eq!(bigint("-9223372036854775809").signed_limb_len(), 2);
1148 assert_eq!(
1149 bigint("170141183460469231731687303715884105727").signed_limb_len(),
1150 2
1151 );
1152 assert_eq!(
1153 bigint("170141183460469231731687303715884105728").signed_limb_len(),
1154 3
1155 );
1156 assert_eq!(
1157 bigint("-170141183460469231731687303715884105728").signed_limb_len(),
1158 2
1159 );
1160 assert_eq!(
1161 bigint("-170141183460469231731687303715884105729").signed_limb_len(),
1162 3
1163 );
1164 }
1165
1166 #[test]
1167 fn copy_free_signed_limb_len_matches_decremented_magnitude_formula() {
1168 use num_bigint::{BigInt, Sign};
1169 fn reference(value: &BigInt) -> u64 {
1170 let bits = match value.sign() {
1171 Sign::NoSign => 1,
1172 Sign::Plus => value.bits() + 1,
1173 Sign::Minus => (value.magnitude() - 1u8).bits() + 1,
1174 };
1175 bits.div_ceil(BIGINT_LIMB_BITS).max(1)
1176 }
1177 let mut samples = vec![BigInt::from(0)];
1178 for shift in 0..300_u32 {
1179 let power = BigInt::from(1) << shift;
1180 samples.extend([&power - 1, power.clone(), &power + 1]);
1181 }
1182 let mut state = BigInt::from(0x9e37_79b9_7f4a_7c15_u64);
1183 for _ in 0..200 {
1184 state = (&state * 6_364_136_223_846_793_005_u64 + 1_442_695_040_888_963_407_u64)
1185 % (BigInt::from(1) << 400_u32);
1186 samples.push(&state >> (state.bits() % 397));
1187 }
1188 for magnitude in samples {
1189 for value in [magnitude.clone(), -magnitude] {
1190 assert_eq!(super::signed_limb_len(&value), reference(&value), "{value}");
1191 }
1192 }
1193 }
1194
1195 #[test]
1196 fn cmp_f64_matches_exact_arbitrary_precision_comparison() {
1197 use num_bigint::BigInt;
1198 use num_traits::FromPrimitive;
1199 use std::cmp::Ordering;
1200 fn reference(left: &JsBigInt, number: f64) -> Option<Ordering> {
1201 if number.is_nan() {
1202 return None;
1203 }
1204 if number.is_infinite() {
1205 return Some(if number > 0.0 {
1206 Ordering::Less
1207 } else {
1208 Ordering::Greater
1209 });
1210 }
1211 let truncated = BigInt::from_f64(number.trunc()).unwrap();
1212 Some(
1213 left.to_bigint()
1214 .cmp(&truncated)
1215 .then(0.0.partial_cmp(&number.fract()).unwrap()),
1216 )
1217 }
1218 let mut values = vec![JsBigInt::from(i64::MIN), JsBigInt::from(i64::MAX)];
1219 for text in [
1220 "0",
1221 "1",
1222 "9007199254740993",
1223 "9223372036854775808",
1224 "1000000000000000000000000000000",
1225 ] {
1226 for sign in ["", "-"] {
1227 let value = bigint(&format!("{sign}{text}"));
1228 values.extend([value.sub(&JsBigInt::one()).unwrap(), value.clone()]);
1229 values.push(value.add(&JsBigInt::one()).unwrap());
1230 }
1231 }
1232 let mut numbers = vec![
1233 f64::NAN,
1234 f64::INFINITY,
1235 -0.0,
1236 0.5,
1237 1.5,
1238 1e20,
1239 1e30,
1240 f64::MAX,
1241 ];
1242 numbers.extend([2f64.powi(53), 2f64.powi(63), 2f64.powi(64), 2f64.powi(99)]);
1243 numbers.extend(values.iter().map(JsBigInt::to_f64));
1244 for number in numbers.clone() {
1245 let next = f64::from_bits(number.to_bits() + 1);
1246 numbers.extend([next, f64::from_bits(number.to_bits().saturating_sub(1))]);
1247 }
1248 for value in &values {
1249 for number in numbers.iter().flat_map(|number| [*number, -*number]) {
1250 assert_eq!(
1251 value.cmp_f64(number),
1252 reference(value, number),
1253 "{value} vs {number}"
1254 );
1255 }
1256 }
1257 }
1258
1259 #[test]
1260 fn parses_string_to_bigint_forms_and_formats_all_radices() {
1261 assert_eq!(bigint(""), JsBigInt::zero());
1262 assert_eq!(bigint("\u{00a0} +123 \u{feff}"), JsBigInt::from(123));
1263 assert_eq!(bigint("-00123"), JsBigInt::from(-123));
1264 assert_eq!(bigint("0b101010"), JsBigInt::from(42));
1265 assert_eq!(bigint("0O77"), JsBigInt::from(63));
1266 assert_eq!(bigint("0xabcdef"), JsBigInt::from(0x00ab_cdef));
1267
1268 let large = bigint("1267650600228229401496703205376");
1269 assert_eq!(
1270 large.to_string_radix(10).unwrap(),
1271 "1267650600228229401496703205376"
1272 );
1273 assert_eq!(
1274 large.to_string_radix(8).unwrap(),
1275 "2000000000000000000000000000000000"
1276 );
1277 assert_eq!(
1278 large.to_string_radix(16).unwrap(),
1279 "10000000000000000000000000"
1280 );
1281 assert_eq!(
1282 bigint("-1267650600228229401496703205376")
1283 .to_string_radix(36)
1284 .unwrap(),
1285 "-3ewfdnca0n6ld1ggvfgg"
1286 );
1287
1288 assert_eq!(JsBigInt::parse_radix("z", 36).unwrap(), JsBigInt::from(35));
1289 assert_eq!(
1290 JsBigInt::parse_radix("+ff", 16).unwrap(),
1291 JsBigInt::from(255)
1292 );
1293 assert_eq!(
1294 JsBigInt::parse_radix("-ff", 16).unwrap(),
1295 JsBigInt::from(-255)
1296 );
1297 }
1298
1299 #[test]
1300 fn parsing_preserves_the_upstream_conservative_allocation_guard() {
1301 for (prefix, digit, first_failing_length) in [
1302 ("0b", '1', 1_048_576),
1303 ("0o", '7', 349_526),
1304 ("", '9', 310_689),
1305 ("0x", 'f', 262_144),
1306 ] {
1307 let source = format!("{prefix}{}", digit.to_string().repeat(first_failing_length));
1308 assert_eq!(
1309 JsBigInt::parse_js_string(&source),
1310 Err(BigIntError::AllocationTooLarge),
1311 "radix spelling starting with {prefix:?} crossed the wrong allocation boundary"
1312 );
1313 }
1314
1315 let leading_zeroes = format!("0x{}1", "0".repeat(262_144));
1316 assert_eq!(
1317 JsBigInt::parse_js_string(&leading_zeroes).unwrap(),
1318 JsBigInt::one(),
1319 "leading zeroes must be discarded before the conservative guard"
1320 );
1321 }
1322
1323 #[test]
1324 fn rejects_invalid_string_to_bigint_forms() {
1325 for source in ["+", "-", "-0x1", "+0x1", "0x", "1_0", "1n", "\0a", " 12 r "] {
1326 assert_eq!(
1327 JsBigInt::parse_js_string(source),
1328 Err(BigIntError::InvalidSyntax)
1329 );
1330 }
1331 assert_eq!(
1332 JsBigInt::parse_radix("2", 2),
1333 Err(BigIntError::InvalidSyntax)
1334 );
1335 assert_eq!(
1336 JsBigInt::parse_radix("1", 1),
1337 Err(BigIntError::InvalidRadix(1))
1338 );
1339 assert_eq!(
1340 JsBigInt::one().to_string_radix(37),
1341 Err(BigIntError::InvalidRadix(37))
1342 );
1343 }
1344
1345 #[test]
1346 fn arithmetic_matches_upstream_large_integer_vectors() {
1347 let three_to_100 = JsBigInt::from(3).pow(&JsBigInt::from(100)).unwrap();
1348 assert_eq!(
1349 three_to_100.to_string(),
1350 "515377520732011331036461129765621272702107522001"
1351 );
1352
1353 let dividend = bigint("3213213213213213432453243");
1354 let divisor = bigint("123434343439");
1355 assert_eq!(
1356 dividend.div(&divisor).unwrap().to_string(),
1357 "26031760073331"
1358 );
1359 assert_eq!(dividend.rem(&divisor).unwrap().to_string(), "26953727934");
1360 let negative = dividend.neg().unwrap();
1361 assert_eq!(
1362 negative.div(&divisor).unwrap().to_string(),
1363 "-26031760073331"
1364 );
1365 assert_eq!(negative.rem(&divisor).unwrap().to_string(), "-26953727934");
1366
1367 assert_eq!(
1368 JsBigInt::from(-2)
1369 .pow(&JsBigInt::from(127))
1370 .unwrap()
1371 .to_string(),
1372 "-170141183460469231731687303715884105728"
1373 );
1374 assert_eq!(
1375 JsBigInt::from(7)
1376 .pow(&JsBigInt::from(20))
1377 .unwrap()
1378 .to_string(),
1379 "79792266297612001"
1380 );
1381 }
1382
1383 #[test]
1384 fn division_promotes_min_overflow_and_reports_zero() {
1385 let promoted = JsBigInt::from(i64::MIN).div(&JsBigInt::from(-1)).unwrap();
1386 assert_eq!(promoted.to_string(), "9223372036854775808");
1387 assert!(!promoted.is_short());
1388 assert_eq!(
1389 JsBigInt::from(i64::MIN).rem(&JsBigInt::from(-1)).unwrap(),
1390 JsBigInt::zero()
1391 );
1392 assert_eq!(
1393 JsBigInt::one().div(&JsBigInt::zero()),
1394 Err(BigIntError::DivisionByZero)
1395 );
1396 assert_eq!(
1397 JsBigInt::one().rem(&JsBigInt::zero()),
1398 Err(BigIntError::DivisionByZero)
1399 );
1400 }
1401
1402 #[test]
1403 fn pow_handles_sign_shortcuts_and_explicit_errors() {
1404 assert_eq!(
1405 JsBigInt::from(123).pow(&JsBigInt::zero()).unwrap(),
1406 JsBigInt::one()
1407 );
1408 assert_eq!(
1409 JsBigInt::from(-1)
1410 .pow(&bigint("999999999999999999999999999999999999999"))
1411 .unwrap(),
1412 JsBigInt::from(-1)
1413 );
1414 assert_eq!(
1415 JsBigInt::from(2).pow(&JsBigInt::from(-1)),
1416 Err(BigIntError::NegativeExponent)
1417 );
1418 assert_eq!(
1419 JsBigInt::from(2).pow(&bigint("2147483648")),
1420 Err(BigIntError::BigIntTooLarge)
1421 );
1422
1423 let half_width_base = JsBigInt::one()
1424 .shl(&JsBigInt::from(MAX_BIGINT_BITS / 2 - 2))
1425 .unwrap()
1426 .add(&JsBigInt::one())
1427 .unwrap();
1428 assert_eq!(
1429 half_width_base.pow(&JsBigInt::from(2)).unwrap(),
1430 half_width_base.mul(&half_width_base).unwrap()
1431 );
1432
1433 let max_power_of_two = JsBigInt::from(2)
1434 .pow(&JsBigInt::from(MAX_BIGINT_BITS - 2))
1435 .unwrap();
1436 assert_eq!(max_power_of_two.signed_limb_len(), MAX_BIGINT_LIMBS);
1437 assert_eq!(
1438 JsBigInt::from(2).pow(&JsBigInt::from(MAX_BIGINT_BITS - 1)),
1439 Err(BigIntError::AllocationTooLarge)
1440 );
1441 assert_eq!(
1442 JsBigInt::from(-2).pow(&JsBigInt::from(MAX_BIGINT_BITS)),
1443 Err(BigIntError::AllocationTooLarge)
1444 );
1445 assert_eq!(
1446 JsBigInt::from(2).pow(&JsBigInt::from(MAX_BIGINT_BITS + 1)),
1447 Err(BigIntError::BigIntTooLarge)
1448 );
1449 assert!(
1450 JsBigInt::from(-2)
1451 .pow(&JsBigInt::from(MAX_BIGINT_BITS - 1))
1452 .unwrap()
1453 .is_negative()
1454 );
1455 }
1456
1457 #[test]
1458 fn shifts_match_arithmetic_and_negative_count_semantics() {
1459 let value = bigint("0x5a4653ca673768565b41f775");
1460 assert_eq!(
1461 value.shl(&JsBigInt::from(78)).unwrap().to_string(),
1462 "8443945299673273647701379149826607537748959488376832"
1463 );
1464 assert_eq!(
1465 value.shr(&JsBigInt::from(78)).unwrap(),
1466 JsBigInt::from(92441)
1467 );
1468 assert_eq!(
1469 value.neg().unwrap().shr(&JsBigInt::from(78)).unwrap(),
1470 JsBigInt::from(-92442)
1471 );
1472 assert_eq!(
1473 JsBigInt::from(8).shl(&JsBigInt::from(-1)).unwrap(),
1474 JsBigInt::from(4)
1475 );
1476 assert_eq!(
1477 JsBigInt::from(8).shr(&JsBigInt::from(-2)).unwrap(),
1478 JsBigInt::from(32)
1479 );
1480 }
1481
1482 #[test]
1483 fn excessive_left_shift_is_an_explicit_error_but_right_shift_saturates() {
1484 let huge = JsBigInt::from(MAX_BIGINT_BITS + 1);
1485 assert_eq!(JsBigInt::one().shl(&huge), Err(BigIntError::ShiftTooLarge));
1486 assert_eq!(JsBigInt::zero().shl(&huge).unwrap(), JsBigInt::zero());
1487 assert_eq!(JsBigInt::one().shr(&huge).unwrap(), JsBigInt::zero());
1488 assert_eq!(JsBigInt::from(-1).shr(&huge).unwrap(), JsBigInt::from(-1));
1489 assert_eq!(
1490 JsBigInt::one().shr(&huge.neg().unwrap()),
1491 Err(BigIntError::ShiftTooLarge)
1492 );
1493 }
1494
1495 #[test]
1496 fn shift_preserves_quickjs_unguarded_sign_limb_extension() {
1497 let boundary_count = JsBigInt::from(MAX_BIGINT_BITS - 1);
1498 let boundary = JsBigInt::one().shl(&boundary_count).unwrap();
1499 assert_eq!(boundary.signed_limb_len(), MAX_BIGINT_LIMBS + 1);
1500 assert_eq!(boundary.shr(&boundary_count).unwrap(), JsBigInt::one());
1501
1502 assert_eq!(
1503 boundary.shl(&JsBigInt::zero()),
1504 Err(BigIntError::ShiftTooLarge)
1505 );
1506 assert_eq!(
1507 boundary.shr(&JsBigInt::zero()),
1508 Err(BigIntError::ShiftTooLarge)
1509 );
1510 assert_eq!(
1511 boundary.bit_and(&JsBigInt::from(-1)),
1512 Err(BigIntError::AllocationTooLarge)
1513 );
1514 assert_eq!(boundary.bit_not(), Err(BigIntError::AllocationTooLarge));
1515 assert_eq!(
1516 boundary.add(&JsBigInt::zero()),
1517 Err(BigIntError::AllocationTooLarge)
1518 );
1519 assert_eq!(boundary.neg(), Err(BigIntError::AllocationTooLarge));
1520
1521 assert_eq!(
1522 JsBigInt::one().shl(&JsBigInt::from(MAX_BIGINT_BITS)),
1523 Err(BigIntError::ShiftTooLarge)
1524 );
1525 assert_eq!(
1526 JsBigInt::zero().shl(&JsBigInt::from(u64::MAX)).unwrap(),
1527 JsBigInt::zero()
1528 );
1529
1530 let near_boundary = JsBigInt::one()
1531 .shl(&JsBigInt::from(MAX_BIGINT_BITS - 2))
1532 .unwrap();
1533 assert_eq!(near_boundary.add(&JsBigInt::zero()).unwrap(), near_boundary);
1534 assert_eq!(
1535 near_boundary.bit_and(&JsBigInt::from(-1)).unwrap(),
1536 near_boundary
1537 );
1538 assert_eq!(
1539 near_boundary.mul(&JsBigInt::zero()),
1540 Err(BigIntError::AllocationTooLarge)
1541 );
1542 assert_eq!(
1543 near_boundary.div(&JsBigInt::one()),
1544 Err(BigIntError::AllocationTooLarge)
1545 );
1546 assert_eq!(
1547 near_boundary.rem(&JsBigInt::one()),
1548 Err(BigIntError::AllocationTooLarge)
1549 );
1550 assert_eq!(near_boundary.pow(&JsBigInt::one()).unwrap(), near_boundary);
1551 let added_boundary = near_boundary.add(&near_boundary).unwrap();
1552 assert_eq!(added_boundary, boundary);
1553
1554 let negative_boundary = JsBigInt::from(-1)
1555 .shl(&JsBigInt::from(MAX_BIGINT_BITS - 1))
1556 .unwrap();
1557 assert_eq!(negative_boundary.neg().unwrap(), boundary);
1558 assert_eq!(
1559 boundary.pow(&JsBigInt::one()),
1560 Err(BigIntError::AllocationTooLarge)
1561 );
1562 assert_eq!(boundary.pow(&JsBigInt::zero()).unwrap(), JsBigInt::one());
1563 }
1564
1565 #[test]
1566 fn bitwise_operations_use_infinite_twos_complement() {
1567 assert_eq!(
1568 JsBigInt::from(0x5a65_3ca6).bit_not().unwrap(),
1569 JsBigInt::from(-1_516_584_103)
1570 );
1571 let left = JsBigInt::from(0x5a46_3ca6);
1572 let right = JsBigInt::from(0x6737_6856);
1573 assert_eq!(left.bit_or(&right).unwrap(), JsBigInt::from(2_138_537_206));
1574 assert_eq!(left.bit_and(&right).unwrap(), JsBigInt::from(1_107_699_718));
1575 assert_eq!(left.bit_xor(&right).unwrap(), JsBigInt::from(1_030_837_488));
1576
1577 let huge = bigint("1208925819614629174706176"); assert_eq!(
1579 huge.bit_or(&JsBigInt::from(-1)).unwrap(),
1580 JsBigInt::from(-1)
1581 );
1582 assert_eq!(huge.bit_and(&JsBigInt::from(-1)).unwrap(), huge);
1583 }
1584
1585 #[test]
1586 fn comparison_is_numeric_across_storage_classes() {
1587 let short = JsBigInt::from(i64::MAX);
1588 let heap = short.add(&JsBigInt::one()).unwrap();
1589 assert!(short < heap);
1590 assert!(bigint("-9223372036854775809") < JsBigInt::from(i64::MIN));
1591 assert_eq!(
1592 bigint("123456789012345678901234567890"),
1593 bigint("123456789012345678901234567890")
1594 );
1595 }
1596
1597 #[test]
1598 fn conversion_to_f64_rounds_ties_to_even_and_overflows_like_quickjs() {
1599 assert_eq!(JsBigInt::from(42).to_f64(), 42.0);
1600 assert_eq!(bigint("9007199254740993").to_f64(), 9_007_199_254_740_992.0);
1601 assert_eq!(bigint("9007199254740995").to_f64(), 9_007_199_254_740_996.0);
1602 assert_eq!(
1603 bigint("-9007199254740995").to_f64(),
1604 -9_007_199_254_740_996.0
1605 );
1606
1607 let two_to_1023 = JsBigInt::one().shl(&JsBigInt::from(1023)).unwrap();
1608 assert_eq!(two_to_1023.to_f64(), 2.0_f64.powi(1023));
1609 let two_to_1024 = two_to_1023.shl(&JsBigInt::one()).unwrap();
1610 assert_eq!(two_to_1024.to_f64(), f64::INFINITY);
1611 assert_eq!(two_to_1024.neg().unwrap().to_f64(), f64::NEG_INFINITY);
1612 }
1613
1614 #[test]
1615 fn as_int_n_truncates_and_sign_extends() {
1616 assert_eq!(JsBigInt::from(257).as_int_n(8).unwrap(), JsBigInt::one());
1617 assert_eq!(JsBigInt::from(255).as_int_n(8).unwrap(), JsBigInt::from(-1));
1618 assert_eq!(
1619 JsBigInt::from(-129).as_int_n(8).unwrap(),
1620 JsBigInt::from(127)
1621 );
1622 assert_eq!(
1623 bigint("18446744073709551616").as_int_n(65).unwrap(),
1624 bigint("-18446744073709551616")
1625 );
1626 assert_eq!(
1627 bigint("36893488147419103231").as_int_n(65).unwrap(),
1628 JsBigInt::from(-1)
1629 );
1630 assert_eq!(
1631 bigint("999999999999999999999").as_int_n(0).unwrap(),
1632 JsBigInt::zero()
1633 );
1634 }
1635
1636 #[test]
1637 fn quickjs_2026_as_uint_n_preserves_signed_limb_quirks() {
1638 assert_eq!(
1641 JsBigInt::from(-1).as_uint_n(64).unwrap(),
1642 JsBigInt::from(-1)
1643 );
1644 assert_eq!(
1645 JsBigInt::from(-1).as_uint_n(128).unwrap(),
1646 JsBigInt::from(-1)
1647 );
1648 assert_eq!(
1649 bigint("9223372036854775808").as_uint_n(64).unwrap(),
1650 JsBigInt::from(i64::MIN)
1651 );
1652 assert_eq!(
1653 bigint("18446744073709551615").as_uint_n(64).unwrap(),
1654 JsBigInt::from(-1)
1655 );
1656 assert_eq!(
1657 bigint("-18446744073709551616").as_uint_n(128).unwrap(),
1658 bigint("-18446744073709551616")
1659 );
1660 assert_eq!(
1663 bigint("-18446744073709551616").as_uint_n(65).unwrap(),
1664 bigint("18446744073709551616")
1665 );
1666 }
1667
1668 #[test]
1669 fn as_n_preserves_the_extended_limb_allocation_gap() {
1670 let extended = JsBigInt::one()
1671 .shl(&JsBigInt::from(MAX_BIGINT_BITS - 1))
1672 .unwrap();
1673 assert_eq!(extended.signed_limb_len(), MAX_BIGINT_LIMBS + 1);
1674
1675 assert_eq!(
1676 extended.as_uint_n(MAX_BIGINT_BITS - 1).unwrap(),
1677 JsBigInt::zero()
1678 );
1679 assert_eq!(
1680 extended.as_int_n(MAX_BIGINT_BITS - 1).unwrap(),
1681 JsBigInt::zero()
1682 );
1683
1684 let at_nominal_limit = extended.as_uint_n(MAX_BIGINT_BITS).unwrap();
1685 let exact_negative = JsBigInt::normalize(-extended.to_bigint());
1686 assert_eq!(at_nominal_limit, exact_negative);
1687 assert_eq!(extended.as_int_n(MAX_BIGINT_BITS).unwrap(), exact_negative);
1688
1689 for bits in MAX_BIGINT_BITS + 1..MAX_BIGINT_BITS + BIGINT_LIMB_BITS {
1690 assert_eq!(
1691 extended.as_uint_n(bits),
1692 Err(BigIntError::AllocationTooLarge)
1693 );
1694 assert_eq!(
1695 extended.as_int_n(bits),
1696 Err(BigIntError::AllocationTooLarge)
1697 );
1698 }
1699 assert_eq!(
1700 extended
1701 .as_uint_n(MAX_BIGINT_BITS + BIGINT_LIMB_BITS)
1702 .unwrap(),
1703 extended
1704 );
1705 assert_eq!(
1706 extended
1707 .as_int_n(MAX_BIGINT_BITS + BIGINT_LIMB_BITS + 1)
1708 .unwrap(),
1709 extended
1710 );
1711 assert_eq!(extended.as_uint_n(u64::MAX).unwrap(), extended);
1712 }
1713
1714 #[test]
1715 fn integral_f64_conversion_preserves_the_exact_binary_value() {
1716 assert_eq!(JsBigInt::from_integral_f64(-0.0).unwrap(), JsBigInt::zero());
1717 assert_eq!(
1718 JsBigInt::from_integral_f64(9_007_199_254_740_991.0).unwrap(),
1719 bigint("9007199254740991")
1720 );
1721 assert_eq!(
1722 JsBigInt::from_integral_f64(9_007_199_254_740_992.0).unwrap(),
1723 bigint("9007199254740992")
1724 );
1725 assert_eq!(
1726 JsBigInt::from_integral_f64(9_007_199_254_740_994.0).unwrap(),
1727 bigint("9007199254740994")
1728 );
1729
1730 let max_finite = JsBigInt::one()
1731 .shl(&JsBigInt::from(1024))
1732 .unwrap()
1733 .sub(&JsBigInt::one().shl(&JsBigInt::from(971)).unwrap())
1734 .unwrap();
1735 assert_eq!(JsBigInt::from_integral_f64(f64::MAX).unwrap(), max_finite);
1736 assert_eq!(
1737 JsBigInt::from_integral_f64(f64::MIN).unwrap(),
1738 max_finite.neg().unwrap()
1739 );
1740
1741 assert!(JsBigInt::from_integral_f64(1.5).is_none());
1742 assert!(JsBigInt::from_integral_f64(f64::NAN).is_none());
1743 assert!(JsBigInt::from_integral_f64(f64::INFINITY).is_none());
1744 assert!(JsBigInt::from_integral_f64(f64::NEG_INFINITY).is_none());
1745 }
1746
1747 #[test]
1748 fn arbitrary_precision_is_not_capped_at_i128() {
1749 let value = JsBigInt::from(3).pow(&JsBigInt::from(1000)).unwrap();
1750 assert!(value.magnitude_bits() > 128);
1751 let text = value.to_string();
1752 assert_eq!(text.len(), 478);
1753 assert!(text.starts_with("1322070819480806636890455259752144365965"));
1754 assert_eq!(JsBigInt::parse_js_string(&text).unwrap(), value);
1755 }
1756}