use super::primitive::is_ecmascript_whitespace;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::rc::Rc;
use std::str::FromStr;
use num_bigint::{BigInt, BigUint, Sign};
use num_traits::{FromPrimitive, One, ToPrimitive, Zero};
pub const MAX_BIGINT_BITS: u64 = 1024 * 1024;
pub const BIGINT_LIMB_BITS: u64 = 64;
const MAX_BIGINT_LIMBS: u64 = MAX_BIGINT_BITS / BIGINT_LIMB_BITS;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BigIntError {
InvalidSyntax,
InvalidRadix(u32),
DivisionByZero,
NegativeExponent,
BigIntTooLarge,
AllocationTooLarge,
ShiftTooLarge,
}
impl fmt::Display for BigIntError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSyntax => formatter.write_str("invalid BigInt syntax"),
Self::InvalidRadix(radix) => {
write!(
formatter,
"invalid BigInt radix {radix}; expected 2 through 36"
)
}
Self::DivisionByZero => formatter.write_str("BigInt division by zero"),
Self::NegativeExponent => formatter.write_str("BigInt negative exponent"),
Self::BigIntTooLarge => formatter.write_str("BigInt is too large"),
Self::AllocationTooLarge => formatter.write_str("BigInt is too large to allocate"),
Self::ShiftTooLarge => formatter.write_str("BigInt shift is too large"),
}
}
}
impl std::error::Error for BigIntError {}
#[derive(Clone, Eq, Hash, PartialEq)]
enum BigIntRepr {
Short(i64),
Heap(Rc<BigInt>),
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct JsBigInt(BigIntRepr);
impl JsBigInt {
#[must_use]
pub fn same_representation(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left == right,
(BigIntRepr::Heap(left), BigIntRepr::Heap(right)) => Rc::ptr_eq(left, right),
(BigIntRepr::Short(_), BigIntRepr::Heap(_))
| (BigIntRepr::Heap(_), BigIntRepr::Short(_)) => false,
}
}
#[must_use]
pub const fn zero() -> Self {
Self(BigIntRepr::Short(0))
}
#[must_use]
pub const fn one() -> Self {
Self(BigIntRepr::Short(1))
}
pub fn from_bigint(value: BigInt) -> Result<Self, BigIntError> {
if signed_limb_len(&value) > MAX_BIGINT_LIMBS {
return Err(BigIntError::BigIntTooLarge);
}
Ok(Self::normalize(value))
}
pub(crate) fn from_decimal_unchecked(text: &str) -> Option<Self> {
BigInt::parse_bytes(text.as_bytes(), 10).map(Self::normalize)
}
#[must_use]
pub fn from_integral_f64(value: f64) -> Option<Self> {
if !value.is_finite() || value.fract() != 0.0 {
return None;
}
BigInt::from_f64(value).map(Self::normalize)
}
pub fn parse_js_string(source: &str) -> Result<Self, BigIntError> {
let source = source.trim_matches(|character: char| {
u16::try_from(character).is_ok_and(is_ecmascript_whitespace)
});
if source.is_empty() {
return Ok(Self::zero());
}
let bytes = source.as_bytes();
match bytes[0] {
b'+' | b'-' => {
let negative = bytes[0] == b'-';
let digits = &source[1..];
parse_digits(digits, 10, negative)
}
_ if bytes.len() >= 2 && bytes[0] == b'0' => {
let radix = match bytes[1] {
b'b' | b'B' => Some(2),
b'o' | b'O' => Some(8),
b'x' | b'X' => Some(16),
_ => None,
};
if let Some(radix) = radix {
parse_digits(&source[2..], radix, false)
} else {
parse_digits(source, 10, false)
}
}
_ => parse_digits(source, 10, false),
}
}
pub fn parse_radix(source: &str, radix: u32) -> Result<Self, BigIntError> {
validate_radix(radix)?;
let (negative, digits) = match source.as_bytes().first().copied() {
Some(b'-') => (true, &source[1..]),
Some(b'+') => (false, &source[1..]),
_ => (false, source),
};
parse_digits(digits, radix, negative)
}
pub fn to_string_radix(&self, radix: u32) -> Result<String, BigIntError> {
validate_radix(radix)?;
Ok(match &self.0 {
BigIntRepr::Short(value) => short_to_string_radix(*value, radix),
BigIntRepr::Heap(value) => value.to_str_radix(radix),
})
}
#[must_use]
pub const fn is_short(&self) -> bool {
matches!(self.0, BigIntRepr::Short(_))
}
#[must_use]
pub const fn as_i64(&self) -> Option<i64> {
match self.0 {
BigIntRepr::Short(value) => Some(value),
BigIntRepr::Heap(_) => None,
}
}
#[must_use]
pub fn to_bigint(&self) -> BigInt {
match &self.0 {
BigIntRepr::Short(value) => BigInt::from(*value),
BigIntRepr::Heap(value) => value.as_ref().clone(),
}
}
#[must_use]
pub fn to_f64(&self) -> f64 {
match &self.0 {
BigIntRepr::Short(value) => *value as f64,
BigIntRepr::Heap(value) => value
.to_f64()
.expect("an arbitrary-precision integer always has an f64 image"),
}
}
#[must_use]
pub fn cmp_f64(&self, number: f64) -> Option<Ordering> {
const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
if number.is_nan() {
return None;
}
if (-TWO_POW_63..TWO_POW_63).contains(&number) {
let ordering = match &self.0 {
BigIntRepr::Short(value) => value.cmp(&(number.trunc() as i64)),
BigIntRepr::Heap(value) if value.sign() == Sign::Minus => Ordering::Less,
BigIntRepr::Heap(_) => Ordering::Greater,
};
return Some(ordering.then(0.0.partial_cmp(&number.fract())?));
}
match &self.0 {
BigIntRepr::Heap(value)
if number.is_finite() && (value.sign() == Sign::Minus) == (number < 0.0) =>
{
let number_bits = ((number.to_bits() >> 52) & 0x7ff) - 1022;
let magnitude = value.bits().cmp(&number_bits).then_with(|| {
let number = BigUint::from_f64(number.abs()).expect("finite Number");
value.magnitude().cmp(&number)
});
Some(if number < 0.0 {
magnitude.reverse()
} else {
magnitude
})
}
_ => Some(if number > 0.0 {
Ordering::Less
} else {
Ordering::Greater
}),
}
}
#[must_use]
pub fn magnitude_bits(&self) -> u64 {
match &self.0 {
BigIntRepr::Short(value) => {
let magnitude = value.unsigned_abs();
u64::from(u64::BITS - magnitude.leading_zeros())
}
BigIntRepr::Heap(value) => value.bits(),
}
}
#[must_use]
pub fn signed_limb_len(&self) -> u64 {
match &self.0 {
BigIntRepr::Short(_) => 1,
BigIntRepr::Heap(value) => signed_limb_len(value),
}
}
pub fn exceeds_allocation_limit(&self) -> bool {
self.signed_limb_len() > MAX_BIGINT_LIMBS
}
#[must_use]
pub fn is_zero(&self) -> bool {
match &self.0 {
BigIntRepr::Short(value) => *value == 0,
BigIntRepr::Heap(value) => value.is_zero(),
}
}
#[must_use]
pub fn is_negative(&self) -> bool {
match &self.0 {
BigIntRepr::Short(value) => *value < 0,
BigIntRepr::Heap(value) => value.sign() == Sign::Minus,
}
}
pub fn add(&self, rhs: &Self) -> Result<Self, BigIntError> {
let short_result = match (&self.0, &rhs.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_add(*right),
_ => None,
};
if let Some(result) = short_result {
return Ok(Self::from(result));
}
self.ensure_operands_allocatable(rhs)?;
self.checked_extending_binary(rhs, |left, right| left + right)
}
pub fn sub(&self, rhs: &Self) -> Result<Self, BigIntError> {
let short_result = match (&self.0, &rhs.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_sub(*right),
_ => None,
};
if let Some(result) = short_result {
return Ok(Self::from(result));
}
self.ensure_operands_allocatable(rhs)?;
self.checked_extending_binary(rhs, |left, right| left - right)
}
pub fn update_decrement(&self) -> Result<Self, BigIntError> {
if let BigIntRepr::Short(value) = self.0
&& value != i64::MIN
{
return Ok(Self::from(value - 1));
}
self.add(&Self::from(i64::from(u32::MAX)))
}
pub fn mul(&self, rhs: &Self) -> Result<Self, BigIntError> {
let short_result = match (&self.0, &rhs.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_mul(*right),
_ => None,
};
if let Some(result) = short_result {
return Ok(Self::from(result));
}
self.ensure_operands_allocatable(rhs)?;
let allocated_limbs = self
.signed_limb_len()
.checked_add(rhs.signed_limb_len())
.ok_or(BigIntError::AllocationTooLarge)?;
if allocated_limbs > MAX_BIGINT_LIMBS {
return Err(BigIntError::AllocationTooLarge);
}
self.checked_binary(rhs, |left, right| left * right)
}
pub fn div(&self, rhs: &Self) -> Result<Self, BigIntError> {
if rhs.is_zero() {
return Err(BigIntError::DivisionByZero);
}
let short_result = match (&self.0, &rhs.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.checked_div(*right),
_ => None,
};
if let Some(result) = short_result {
return Ok(Self::from(result));
}
if self
.signed_limb_len()
.checked_add(2)
.is_none_or(|allocated_limbs| allocated_limbs > MAX_BIGINT_LIMBS)
{
return Err(BigIntError::AllocationTooLarge);
}
self.checked_binary(rhs, |left, right| left / right)
}
pub fn rem(&self, rhs: &Self) -> Result<Self, BigIntError> {
if rhs.is_zero() {
return Err(BigIntError::DivisionByZero);
}
if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
if *left == i64::MIN && *right == -1 {
return Ok(Self::zero());
}
return Ok(Self::from(left % right));
}
if self
.signed_limb_len()
.checked_add(2)
.is_none_or(|allocated_limbs| allocated_limbs > MAX_BIGINT_LIMBS)
{
return Err(BigIntError::AllocationTooLarge);
}
self.checked_binary(rhs, |left, right| left % right)
}
pub fn pow(&self, exponent: &Self) -> Result<Self, BigIntError> {
if exponent.is_negative() {
return Err(BigIntError::NegativeExponent);
}
if exponent.is_zero() {
return Ok(Self::one());
}
if exponent.is_one() {
if self.exceeds_allocation_limit() {
return Err(BigIntError::AllocationTooLarge);
}
return Ok(self.clone());
}
if self.is_zero() || self.is_one() {
return Ok(self.clone());
}
if self.is_negative_one() {
return Ok(if exponent.is_odd() {
Self::from(-1)
} else {
Self::one()
});
}
let exponent = exponent
.to_u32()
.filter(|value| *value <= i32::MAX as u32)
.ok_or(BigIntError::BigIntTooLarge)?;
if let Some(base) = self.as_i64() {
let magnitude = base.unsigned_abs();
if magnitude.is_power_of_two() {
let base_shift = u64::from(magnitude.trailing_zeros());
let result_shift = u64::from(exponent) * base_shift;
if result_shift > MAX_BIGINT_BITS {
return Err(BigIntError::BigIntTooLarge);
}
let mut result = BigInt::one()
<< usize::try_from(result_shift)
.expect("bounded QuickJS BigInt power shift fits usize");
if base.is_negative() && exponent & 1 != 0 {
result = -result;
}
return Self::from_bigint(result).map_err(|error| match error {
BigIntError::BigIntTooLarge => BigIntError::AllocationTooLarge,
error => error,
});
}
}
if self.exceeds_allocation_limit() {
return Err(BigIntError::AllocationTooLarge);
}
let highest_bit = u32::BITS - exponent.leading_zeros();
let mut result = self.clone();
for bit in (0..highest_bit - 1).rev() {
result = result.mul(&result)?;
if exponent & (1 << bit) != 0 {
result = result.mul(self)?;
}
}
Ok(result)
}
pub fn neg(&self) -> Result<Self, BigIntError> {
let short_result = match &self.0 {
BigIntRepr::Short(value) => value.checked_neg(),
BigIntRepr::Heap(_) => None,
};
if let Some(result) = short_result {
return Ok(Self::from(result));
}
if self.exceeds_allocation_limit() {
return Err(BigIntError::AllocationTooLarge);
}
let result = -self.as_bigint().as_ref();
if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
return Err(BigIntError::AllocationTooLarge);
}
Ok(Self::normalize(result))
}
pub fn bit_and(&self, rhs: &Self) -> Result<Self, BigIntError> {
if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
return Ok(Self::from(left & right));
}
self.ensure_operands_allocatable(rhs)?;
Ok(self.unchecked_binary(rhs, |left, right| left & right))
}
pub fn bit_or(&self, rhs: &Self) -> Result<Self, BigIntError> {
if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
return Ok(Self::from(left | right));
}
self.ensure_operands_allocatable(rhs)?;
Ok(self.unchecked_binary(rhs, |left, right| left | right))
}
pub fn bit_xor(&self, rhs: &Self) -> Result<Self, BigIntError> {
if let (BigIntRepr::Short(left), BigIntRepr::Short(right)) = (&self.0, &rhs.0) {
return Ok(Self::from(left ^ right));
}
self.ensure_operands_allocatable(rhs)?;
Ok(self.unchecked_binary(rhs, |left, right| left ^ right))
}
pub fn bit_not(&self) -> Result<Self, BigIntError> {
if self.exceeds_allocation_limit() {
return Err(BigIntError::AllocationTooLarge);
}
Ok(match &self.0 {
BigIntRepr::Short(value) => Self::from(!value),
BigIntRepr::Heap(value) => Self::normalize(!value.as_ref()),
})
}
pub fn shl(&self, count: &Self) -> Result<Self, BigIntError> {
self.shift(count, true)
}
pub fn shr(&self, count: &Self) -> Result<Self, BigIntError> {
self.shift(count, false)
}
pub fn as_int_n(&self, bits: u64) -> Result<Self, BigIntError> {
self.as_n(bits, true)
}
pub fn as_uint_n(&self, bits: u64) -> Result<Self, BigIntError> {
self.as_n(bits, false)
}
fn normalize(value: BigInt) -> Self {
if let Some(value) = value.to_i64() {
Self(BigIntRepr::Short(value))
} else {
Self(BigIntRepr::Heap(Rc::new(value)))
}
}
fn as_bigint(&self) -> Cow<'_, BigInt> {
match &self.0 {
BigIntRepr::Short(value) => Cow::Owned(BigInt::from(*value)),
BigIntRepr::Heap(value) => Cow::Borrowed(value.as_ref()),
}
}
fn checked_binary(
&self,
rhs: &Self,
operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
) -> Result<Self, BigIntError> {
let left = self.as_bigint();
let right = rhs.as_bigint();
Self::from_bigint(operation(left.as_ref(), right.as_ref())).map_err(|error| match error {
BigIntError::BigIntTooLarge => BigIntError::AllocationTooLarge,
error => error,
})
}
fn checked_extending_binary(
&self,
rhs: &Self,
operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
) -> Result<Self, BigIntError> {
let left = self.as_bigint();
let right = rhs.as_bigint();
let result = operation(left.as_ref(), right.as_ref());
if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
return Err(BigIntError::AllocationTooLarge);
}
Ok(Self::normalize(result))
}
fn ensure_operands_allocatable(&self, rhs: &Self) -> Result<(), BigIntError> {
if self.exceeds_allocation_limit() || rhs.exceeds_allocation_limit() {
Err(BigIntError::AllocationTooLarge)
} else {
Ok(())
}
}
fn unchecked_binary(
&self,
rhs: &Self,
operation: impl FnOnce(&BigInt, &BigInt) -> BigInt,
) -> Self {
let left = self.as_bigint();
let right = rhs.as_bigint();
let result = operation(left.as_ref(), right.as_ref());
debug_assert!(signed_limb_len(&result) <= MAX_BIGINT_LIMBS);
Self::normalize(result)
}
fn is_one(&self) -> bool {
matches!(self.0, BigIntRepr::Short(1))
}
fn is_negative_one(&self) -> bool {
matches!(self.0, BigIntRepr::Short(-1))
}
fn is_odd(&self) -> bool {
match &self.0 {
BigIntRepr::Short(value) => value & 1 != 0,
BigIntRepr::Heap(value) => value.magnitude().bit(0),
}
}
fn to_u32(&self) -> Option<u32> {
match &self.0 {
BigIntRepr::Short(value) => u32::try_from(*value).ok(),
BigIntRepr::Heap(value) => value.to_u32(),
}
}
fn shift(&self, count: &Self, operator_shifts_left: bool) -> Result<Self, BigIntError> {
let shifts_left = operator_shifts_left != count.is_negative();
let count = count
.magnitude_to_u64()
.unwrap_or(u64::MAX)
.min(i32::MAX as u64);
if shifts_left {
if self.is_zero() {
return Ok(Self::zero());
}
let allocated_limbs = self
.signed_limb_len()
.checked_add(count / BIGINT_LIMB_BITS)
.ok_or(BigIntError::ShiftTooLarge)?;
if allocated_limbs > MAX_BIGINT_LIMBS {
return Err(BigIntError::ShiftTooLarge);
}
let count = usize::try_from(count).map_err(|_| BigIntError::ShiftTooLarge)?;
let result = self.as_bigint().as_ref() << count;
if signed_limb_len(&result) > MAX_BIGINT_LIMBS + 1 {
return Err(BigIntError::ShiftTooLarge);
}
Ok(Self::normalize(result))
} else {
let limb_offset = count / BIGINT_LIMB_BITS;
if limb_offset >= self.signed_limb_len() {
return Ok(self.right_shift_saturation());
}
if self.signed_limb_len() - limb_offset > MAX_BIGINT_LIMBS {
return Err(BigIntError::ShiftTooLarge);
}
let count = usize::try_from(count).expect("bounded BigInt shift fits usize");
Ok(Self::normalize(self.as_bigint().as_ref() >> count))
}
}
fn right_shift_saturation(&self) -> Self {
if self.is_negative() {
Self::from(-1)
} else {
Self::zero()
}
}
fn magnitude_to_u64(&self) -> Option<u64> {
match &self.0 {
BigIntRepr::Short(value) => Some(value.unsigned_abs()),
BigIntRepr::Heap(value) => value.magnitude().to_u64(),
}
}
fn as_n(&self, bits: u64, signed: bool) -> Result<Self, BigIntError> {
if bits == 0 {
return Ok(Self::zero());
}
let storage_bits = self.signed_limb_len() * BIGINT_LIMB_BITS;
if bits >= storage_bits {
return Ok(self.clone());
}
let allocated_limbs = bits
.checked_add(BIGINT_LIMB_BITS - 1)
.ok_or(BigIntError::AllocationTooLarge)?
/ BIGINT_LIMB_BITS;
if allocated_limbs > MAX_BIGINT_LIMBS {
return Err(BigIntError::AllocationTooLarge);
}
let bits_usize = usize::try_from(bits).expect("bounded BigInt bit width fits usize");
let modulus = BigInt::one() << bits_usize;
let mut truncated = self.as_bigint().as_ref() % &modulus;
if truncated.sign() == Sign::Minus {
truncated += &modulus;
}
let interpret_as_signed = signed || bits & (BIGINT_LIMB_BITS - 1) == 0;
if interpret_as_signed && truncated.bit(bits - 1) {
truncated -= modulus;
}
Ok(Self::normalize(truncated))
}
}
impl Default for JsBigInt {
fn default() -> Self {
Self::zero()
}
}
impl From<i64> for JsBigInt {
fn from(value: i64) -> Self {
Self(BigIntRepr::Short(value))
}
}
impl From<i32> for JsBigInt {
fn from(value: i32) -> Self {
Self::from(i64::from(value))
}
}
impl From<u32> for JsBigInt {
fn from(value: u32) -> Self {
Self::from(i64::from(value))
}
}
impl From<u64> for JsBigInt {
fn from(value: u64) -> Self {
if let Ok(value) = i64::try_from(value) {
Self::from(value)
} else {
Self::normalize(BigInt::from(value))
}
}
}
impl From<i128> for JsBigInt {
fn from(value: i128) -> Self {
Self::normalize(BigInt::from(value))
}
}
impl From<u128> for JsBigInt {
fn from(value: u128) -> Self {
Self::normalize(BigInt::from(value))
}
}
impl TryFrom<BigInt> for JsBigInt {
type Error = BigIntError;
fn try_from(value: BigInt) -> Result<Self, Self::Error> {
Self::from_bigint(value)
}
}
impl FromStr for JsBigInt {
type Err = BigIntError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
Self::parse_js_string(source)
}
}
impl Ord for JsBigInt {
fn cmp(&self, other: &Self) -> Ordering {
match (&self.0, &other.0) {
(BigIntRepr::Short(left), BigIntRepr::Short(right)) => left.cmp(right),
(BigIntRepr::Heap(left), BigIntRepr::Heap(right)) => left.cmp(right),
(BigIntRepr::Short(_), BigIntRepr::Heap(heap)) => match heap.sign() {
Sign::Minus => Ordering::Greater,
_ => Ordering::Less,
},
(BigIntRepr::Heap(heap), BigIntRepr::Short(_)) => match heap.sign() {
Sign::Minus => Ordering::Less,
_ => Ordering::Greater,
},
}
}
}
impl PartialOrd for JsBigInt {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for JsBigInt {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
BigIntRepr::Short(value) => value.fmt(formatter),
BigIntRepr::Heap(value) => value.fmt(formatter),
}
}
}
impl fmt::Debug for JsBigInt {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
BigIntRepr::Short(value) => formatter.debug_tuple("ShortBigInt").field(value).finish(),
BigIntRepr::Heap(value) => formatter.debug_tuple("BigInt").field(value).finish(),
}
}
}
fn validate_radix(radix: u32) -> Result<(), BigIntError> {
if (2..=36).contains(&radix) {
Ok(())
} else {
Err(BigIntError::InvalidRadix(radix))
}
}
fn parse_digits(digits: &str, radix: u32, negative: bool) -> Result<JsBigInt, BigIntError> {
validate_radix(radix)?;
if digits.is_empty()
|| !digits
.as_bytes()
.iter()
.all(|byte| is_radix_digit(*byte, radix))
{
return Err(BigIntError::InvalidSyntax);
}
let significant_digits = digits.trim_start_matches('0');
if significant_digits.is_empty() {
return Ok(JsBigInt::zero());
}
let digit_count = u64::try_from(significant_digits.len()).unwrap_or(u64::MAX);
if digit_count > MAX_BIGINT_BITS {
return Err(BigIntError::AllocationTooLarge);
}
let log2_radix = u64::from(u32::BITS - (radix - 1).leading_zeros());
let estimated_bits = if radix == 10 {
(digit_count * 27).div_ceil(8)
} else {
digit_count * log2_radix
};
let estimated_limbs = estimated_bits / BIGINT_LIMB_BITS + 1;
if estimated_limbs > MAX_BIGINT_LIMBS {
return Err(BigIntError::AllocationTooLarge);
}
let magnitude = BigUint::parse_bytes(significant_digits.as_bytes(), radix)
.ok_or(BigIntError::InvalidSyntax)?;
let sign = if negative && !magnitude.is_zero() {
Sign::Minus
} else if magnitude.is_zero() {
Sign::NoSign
} else {
Sign::Plus
};
JsBigInt::from_bigint(BigInt::from_biguint(sign, magnitude))
}
fn is_radix_digit(byte: u8, radix: u32) -> bool {
let value = match byte {
b'0'..=b'9' => u32::from(byte - b'0'),
b'a'..=b'z' => u32::from(byte - b'a') + 10,
b'A'..=b'Z' => u32::from(byte - b'A') + 10,
_ => return false,
};
value < radix
}
fn short_to_string_radix(value: i64, radix: u32) -> String {
const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
if value == 0 {
return "0".to_owned();
}
let negative = value < 0;
let mut magnitude = value.unsigned_abs();
let radix = u64::from(radix);
let mut reversed = Vec::with_capacity(65);
while magnitude != 0 {
let digit = usize::try_from(magnitude % radix).expect("radix digit fits usize");
reversed.push(DIGITS[digit]);
magnitude /= radix;
}
if negative {
reversed.push(b'-');
}
reversed.reverse();
String::from_utf8(reversed).expect("BigInt formatting only emits ASCII")
}
fn signed_limb_len(value: &BigInt) -> u64 {
let signed_bits = match value.sign() {
Sign::NoSign => 1,
Sign::Plus => value.bits() + 1,
Sign::Minus => {
let magnitude = value.magnitude();
let bits = magnitude.bits();
if magnitude.trailing_zeros() == Some(bits - 1) {
bits
} else {
bits + 1
}
}
};
signed_bits.div_ceil(BIGINT_LIMB_BITS).max(1)
}
#[cfg(test)]
mod tests {
use super::{BIGINT_LIMB_BITS, BigIntError, JsBigInt, MAX_BIGINT_BITS, MAX_BIGINT_LIMBS};
fn bigint(source: &str) -> JsBigInt {
JsBigInt::parse_js_string(source).unwrap()
}
#[test]
fn normalizes_short_values_and_promotes_overflow() {
let max = JsBigInt::from(i64::MAX);
let min = JsBigInt::from(i64::MIN);
assert!(max.is_short());
assert!(min.is_short());
let above_max = max.add(&JsBigInt::one()).unwrap();
let below_min = min.sub(&JsBigInt::one()).unwrap();
assert!(!above_max.is_short());
assert!(!below_min.is_short());
assert!(max < above_max && min < above_max && below_min < min && below_min < max);
assert!(above_max > max && below_min < above_max && max > below_min);
assert_eq!(above_max.to_string(), "9223372036854775808");
assert_eq!(below_min.to_string(), "-9223372036854775809");
assert_eq!(above_max.sub(&JsBigInt::one()).unwrap(), max);
assert_eq!(below_min.add(&JsBigInt::one()).unwrap(), min);
assert!(above_max.sub(&JsBigInt::one()).unwrap().is_short());
}
#[test]
fn update_decrement_preserves_pinned_unsigned_opcode_quirk() {
assert_eq!(
JsBigInt::from(4).update_decrement().unwrap(),
JsBigInt::from(3)
);
assert_eq!(
JsBigInt::from(i64::MIN).update_decrement().unwrap(),
bigint("-9223372032559808513")
);
assert_eq!(
bigint("9223372036854775808").update_decrement().unwrap(),
bigint("9223372041149743103")
);
assert_eq!(
bigint("-9223372036854775809").update_decrement().unwrap(),
bigint("-9223372032559808514")
);
assert_eq!(
bigint("9223372036854775808").sub(&JsBigInt::one()).unwrap(),
JsBigInt::from(i64::MAX)
);
}
#[test]
fn signed_limb_width_matches_quickjs_twos_complement_normalization() {
assert_eq!(JsBigInt::from(i64::MAX).signed_limb_len(), 1);
assert_eq!(JsBigInt::from(i64::MIN).signed_limb_len(), 1);
assert_eq!(bigint("9223372036854775808").signed_limb_len(), 2);
assert_eq!(bigint("-9223372036854775809").signed_limb_len(), 2);
assert_eq!(
bigint("170141183460469231731687303715884105727").signed_limb_len(),
2
);
assert_eq!(
bigint("170141183460469231731687303715884105728").signed_limb_len(),
3
);
assert_eq!(
bigint("-170141183460469231731687303715884105728").signed_limb_len(),
2
);
assert_eq!(
bigint("-170141183460469231731687303715884105729").signed_limb_len(),
3
);
}
#[test]
fn copy_free_signed_limb_len_matches_decremented_magnitude_formula() {
use num_bigint::{BigInt, Sign};
fn reference(value: &BigInt) -> u64 {
let bits = match value.sign() {
Sign::NoSign => 1,
Sign::Plus => value.bits() + 1,
Sign::Minus => (value.magnitude() - 1u8).bits() + 1,
};
bits.div_ceil(BIGINT_LIMB_BITS).max(1)
}
let mut samples = vec![BigInt::from(0)];
for shift in 0..300_u32 {
let power = BigInt::from(1) << shift;
samples.extend([&power - 1, power.clone(), &power + 1]);
}
let mut state = BigInt::from(0x9e37_79b9_7f4a_7c15_u64);
for _ in 0..200 {
state = (&state * 6_364_136_223_846_793_005_u64 + 1_442_695_040_888_963_407_u64)
% (BigInt::from(1) << 400_u32);
samples.push(&state >> (state.bits() % 397));
}
for magnitude in samples {
for value in [magnitude.clone(), -magnitude] {
assert_eq!(super::signed_limb_len(&value), reference(&value), "{value}");
}
}
}
#[test]
fn cmp_f64_matches_exact_arbitrary_precision_comparison() {
use num_bigint::BigInt;
use num_traits::FromPrimitive;
use std::cmp::Ordering;
fn reference(left: &JsBigInt, number: f64) -> Option<Ordering> {
if number.is_nan() {
return None;
}
if number.is_infinite() {
return Some(if number > 0.0 {
Ordering::Less
} else {
Ordering::Greater
});
}
let truncated = BigInt::from_f64(number.trunc()).unwrap();
Some(
left.to_bigint()
.cmp(&truncated)
.then(0.0.partial_cmp(&number.fract()).unwrap()),
)
}
let mut values = vec![JsBigInt::from(i64::MIN), JsBigInt::from(i64::MAX)];
for text in [
"0",
"1",
"9007199254740993",
"9223372036854775808",
"1000000000000000000000000000000",
] {
for sign in ["", "-"] {
let value = bigint(&format!("{sign}{text}"));
values.extend([value.sub(&JsBigInt::one()).unwrap(), value.clone()]);
values.push(value.add(&JsBigInt::one()).unwrap());
}
}
let mut numbers = vec![
f64::NAN,
f64::INFINITY,
-0.0,
0.5,
1.5,
1e20,
1e30,
f64::MAX,
];
numbers.extend([2f64.powi(53), 2f64.powi(63), 2f64.powi(64), 2f64.powi(99)]);
numbers.extend(values.iter().map(JsBigInt::to_f64));
for number in numbers.clone() {
let next = f64::from_bits(number.to_bits() + 1);
numbers.extend([next, f64::from_bits(number.to_bits().saturating_sub(1))]);
}
for value in &values {
for number in numbers.iter().flat_map(|number| [*number, -*number]) {
assert_eq!(
value.cmp_f64(number),
reference(value, number),
"{value} vs {number}"
);
}
}
}
#[test]
fn parses_string_to_bigint_forms_and_formats_all_radices() {
assert_eq!(bigint(""), JsBigInt::zero());
assert_eq!(bigint("\u{00a0} +123 \u{feff}"), JsBigInt::from(123));
assert_eq!(bigint("-00123"), JsBigInt::from(-123));
assert_eq!(bigint("0b101010"), JsBigInt::from(42));
assert_eq!(bigint("0O77"), JsBigInt::from(63));
assert_eq!(bigint("0xabcdef"), JsBigInt::from(0x00ab_cdef));
let large = bigint("1267650600228229401496703205376");
assert_eq!(
large.to_string_radix(10).unwrap(),
"1267650600228229401496703205376"
);
assert_eq!(
large.to_string_radix(8).unwrap(),
"2000000000000000000000000000000000"
);
assert_eq!(
large.to_string_radix(16).unwrap(),
"10000000000000000000000000"
);
assert_eq!(
bigint("-1267650600228229401496703205376")
.to_string_radix(36)
.unwrap(),
"-3ewfdnca0n6ld1ggvfgg"
);
assert_eq!(JsBigInt::parse_radix("z", 36).unwrap(), JsBigInt::from(35));
assert_eq!(
JsBigInt::parse_radix("+ff", 16).unwrap(),
JsBigInt::from(255)
);
assert_eq!(
JsBigInt::parse_radix("-ff", 16).unwrap(),
JsBigInt::from(-255)
);
}
#[test]
fn parsing_preserves_the_upstream_conservative_allocation_guard() {
for (prefix, digit, first_failing_length) in [
("0b", '1', 1_048_576),
("0o", '7', 349_526),
("", '9', 310_689),
("0x", 'f', 262_144),
] {
let source = format!("{prefix}{}", digit.to_string().repeat(first_failing_length));
assert_eq!(
JsBigInt::parse_js_string(&source),
Err(BigIntError::AllocationTooLarge),
"radix spelling starting with {prefix:?} crossed the wrong allocation boundary"
);
}
let leading_zeroes = format!("0x{}1", "0".repeat(262_144));
assert_eq!(
JsBigInt::parse_js_string(&leading_zeroes).unwrap(),
JsBigInt::one(),
"leading zeroes must be discarded before the conservative guard"
);
}
#[test]
fn rejects_invalid_string_to_bigint_forms() {
for source in ["+", "-", "-0x1", "+0x1", "0x", "1_0", "1n", "\0a", " 12 r "] {
assert_eq!(
JsBigInt::parse_js_string(source),
Err(BigIntError::InvalidSyntax)
);
}
assert_eq!(
JsBigInt::parse_radix("2", 2),
Err(BigIntError::InvalidSyntax)
);
assert_eq!(
JsBigInt::parse_radix("1", 1),
Err(BigIntError::InvalidRadix(1))
);
assert_eq!(
JsBigInt::one().to_string_radix(37),
Err(BigIntError::InvalidRadix(37))
);
}
#[test]
fn arithmetic_matches_upstream_large_integer_vectors() {
let three_to_100 = JsBigInt::from(3).pow(&JsBigInt::from(100)).unwrap();
assert_eq!(
three_to_100.to_string(),
"515377520732011331036461129765621272702107522001"
);
let dividend = bigint("3213213213213213432453243");
let divisor = bigint("123434343439");
assert_eq!(
dividend.div(&divisor).unwrap().to_string(),
"26031760073331"
);
assert_eq!(dividend.rem(&divisor).unwrap().to_string(), "26953727934");
let negative = dividend.neg().unwrap();
assert_eq!(
negative.div(&divisor).unwrap().to_string(),
"-26031760073331"
);
assert_eq!(negative.rem(&divisor).unwrap().to_string(), "-26953727934");
assert_eq!(
JsBigInt::from(-2)
.pow(&JsBigInt::from(127))
.unwrap()
.to_string(),
"-170141183460469231731687303715884105728"
);
assert_eq!(
JsBigInt::from(7)
.pow(&JsBigInt::from(20))
.unwrap()
.to_string(),
"79792266297612001"
);
}
#[test]
fn division_promotes_min_overflow_and_reports_zero() {
let promoted = JsBigInt::from(i64::MIN).div(&JsBigInt::from(-1)).unwrap();
assert_eq!(promoted.to_string(), "9223372036854775808");
assert!(!promoted.is_short());
assert_eq!(
JsBigInt::from(i64::MIN).rem(&JsBigInt::from(-1)).unwrap(),
JsBigInt::zero()
);
assert_eq!(
JsBigInt::one().div(&JsBigInt::zero()),
Err(BigIntError::DivisionByZero)
);
assert_eq!(
JsBigInt::one().rem(&JsBigInt::zero()),
Err(BigIntError::DivisionByZero)
);
}
#[test]
fn pow_handles_sign_shortcuts_and_explicit_errors() {
assert_eq!(
JsBigInt::from(123).pow(&JsBigInt::zero()).unwrap(),
JsBigInt::one()
);
assert_eq!(
JsBigInt::from(-1)
.pow(&bigint("999999999999999999999999999999999999999"))
.unwrap(),
JsBigInt::from(-1)
);
assert_eq!(
JsBigInt::from(2).pow(&JsBigInt::from(-1)),
Err(BigIntError::NegativeExponent)
);
assert_eq!(
JsBigInt::from(2).pow(&bigint("2147483648")),
Err(BigIntError::BigIntTooLarge)
);
let half_width_base = JsBigInt::one()
.shl(&JsBigInt::from(MAX_BIGINT_BITS / 2 - 2))
.unwrap()
.add(&JsBigInt::one())
.unwrap();
assert_eq!(
half_width_base.pow(&JsBigInt::from(2)).unwrap(),
half_width_base.mul(&half_width_base).unwrap()
);
let max_power_of_two = JsBigInt::from(2)
.pow(&JsBigInt::from(MAX_BIGINT_BITS - 2))
.unwrap();
assert_eq!(max_power_of_two.signed_limb_len(), MAX_BIGINT_LIMBS);
assert_eq!(
JsBigInt::from(2).pow(&JsBigInt::from(MAX_BIGINT_BITS - 1)),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(
JsBigInt::from(-2).pow(&JsBigInt::from(MAX_BIGINT_BITS)),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(
JsBigInt::from(2).pow(&JsBigInt::from(MAX_BIGINT_BITS + 1)),
Err(BigIntError::BigIntTooLarge)
);
assert!(
JsBigInt::from(-2)
.pow(&JsBigInt::from(MAX_BIGINT_BITS - 1))
.unwrap()
.is_negative()
);
}
#[test]
fn shifts_match_arithmetic_and_negative_count_semantics() {
let value = bigint("0x5a4653ca673768565b41f775");
assert_eq!(
value.shl(&JsBigInt::from(78)).unwrap().to_string(),
"8443945299673273647701379149826607537748959488376832"
);
assert_eq!(
value.shr(&JsBigInt::from(78)).unwrap(),
JsBigInt::from(92441)
);
assert_eq!(
value.neg().unwrap().shr(&JsBigInt::from(78)).unwrap(),
JsBigInt::from(-92442)
);
assert_eq!(
JsBigInt::from(8).shl(&JsBigInt::from(-1)).unwrap(),
JsBigInt::from(4)
);
assert_eq!(
JsBigInt::from(8).shr(&JsBigInt::from(-2)).unwrap(),
JsBigInt::from(32)
);
}
#[test]
fn excessive_left_shift_is_an_explicit_error_but_right_shift_saturates() {
let huge = JsBigInt::from(MAX_BIGINT_BITS + 1);
assert_eq!(JsBigInt::one().shl(&huge), Err(BigIntError::ShiftTooLarge));
assert_eq!(JsBigInt::zero().shl(&huge).unwrap(), JsBigInt::zero());
assert_eq!(JsBigInt::one().shr(&huge).unwrap(), JsBigInt::zero());
assert_eq!(JsBigInt::from(-1).shr(&huge).unwrap(), JsBigInt::from(-1));
assert_eq!(
JsBigInt::one().shr(&huge.neg().unwrap()),
Err(BigIntError::ShiftTooLarge)
);
}
#[test]
fn shift_preserves_quickjs_unguarded_sign_limb_extension() {
let boundary_count = JsBigInt::from(MAX_BIGINT_BITS - 1);
let boundary = JsBigInt::one().shl(&boundary_count).unwrap();
assert_eq!(boundary.signed_limb_len(), MAX_BIGINT_LIMBS + 1);
assert_eq!(boundary.shr(&boundary_count).unwrap(), JsBigInt::one());
assert_eq!(
boundary.shl(&JsBigInt::zero()),
Err(BigIntError::ShiftTooLarge)
);
assert_eq!(
boundary.shr(&JsBigInt::zero()),
Err(BigIntError::ShiftTooLarge)
);
assert_eq!(
boundary.bit_and(&JsBigInt::from(-1)),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(boundary.bit_not(), Err(BigIntError::AllocationTooLarge));
assert_eq!(
boundary.add(&JsBigInt::zero()),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(boundary.neg(), Err(BigIntError::AllocationTooLarge));
assert_eq!(
JsBigInt::one().shl(&JsBigInt::from(MAX_BIGINT_BITS)),
Err(BigIntError::ShiftTooLarge)
);
assert_eq!(
JsBigInt::zero().shl(&JsBigInt::from(u64::MAX)).unwrap(),
JsBigInt::zero()
);
let near_boundary = JsBigInt::one()
.shl(&JsBigInt::from(MAX_BIGINT_BITS - 2))
.unwrap();
assert_eq!(near_boundary.add(&JsBigInt::zero()).unwrap(), near_boundary);
assert_eq!(
near_boundary.bit_and(&JsBigInt::from(-1)).unwrap(),
near_boundary
);
assert_eq!(
near_boundary.mul(&JsBigInt::zero()),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(
near_boundary.div(&JsBigInt::one()),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(
near_boundary.rem(&JsBigInt::one()),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(near_boundary.pow(&JsBigInt::one()).unwrap(), near_boundary);
let added_boundary = near_boundary.add(&near_boundary).unwrap();
assert_eq!(added_boundary, boundary);
let negative_boundary = JsBigInt::from(-1)
.shl(&JsBigInt::from(MAX_BIGINT_BITS - 1))
.unwrap();
assert_eq!(negative_boundary.neg().unwrap(), boundary);
assert_eq!(
boundary.pow(&JsBigInt::one()),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(boundary.pow(&JsBigInt::zero()).unwrap(), JsBigInt::one());
}
#[test]
fn bitwise_operations_use_infinite_twos_complement() {
assert_eq!(
JsBigInt::from(0x5a65_3ca6).bit_not().unwrap(),
JsBigInt::from(-1_516_584_103)
);
let left = JsBigInt::from(0x5a46_3ca6);
let right = JsBigInt::from(0x6737_6856);
assert_eq!(left.bit_or(&right).unwrap(), JsBigInt::from(2_138_537_206));
assert_eq!(left.bit_and(&right).unwrap(), JsBigInt::from(1_107_699_718));
assert_eq!(left.bit_xor(&right).unwrap(), JsBigInt::from(1_030_837_488));
let huge = bigint("1208925819614629174706176"); assert_eq!(
huge.bit_or(&JsBigInt::from(-1)).unwrap(),
JsBigInt::from(-1)
);
assert_eq!(huge.bit_and(&JsBigInt::from(-1)).unwrap(), huge);
}
#[test]
fn comparison_is_numeric_across_storage_classes() {
let short = JsBigInt::from(i64::MAX);
let heap = short.add(&JsBigInt::one()).unwrap();
assert!(short < heap);
assert!(bigint("-9223372036854775809") < JsBigInt::from(i64::MIN));
assert_eq!(
bigint("123456789012345678901234567890"),
bigint("123456789012345678901234567890")
);
}
#[test]
fn conversion_to_f64_rounds_ties_to_even_and_overflows_like_quickjs() {
assert_eq!(JsBigInt::from(42).to_f64(), 42.0);
assert_eq!(bigint("9007199254740993").to_f64(), 9_007_199_254_740_992.0);
assert_eq!(bigint("9007199254740995").to_f64(), 9_007_199_254_740_996.0);
assert_eq!(
bigint("-9007199254740995").to_f64(),
-9_007_199_254_740_996.0
);
let two_to_1023 = JsBigInt::one().shl(&JsBigInt::from(1023)).unwrap();
assert_eq!(two_to_1023.to_f64(), 2.0_f64.powi(1023));
let two_to_1024 = two_to_1023.shl(&JsBigInt::one()).unwrap();
assert_eq!(two_to_1024.to_f64(), f64::INFINITY);
assert_eq!(two_to_1024.neg().unwrap().to_f64(), f64::NEG_INFINITY);
}
#[test]
fn as_int_n_truncates_and_sign_extends() {
assert_eq!(JsBigInt::from(257).as_int_n(8).unwrap(), JsBigInt::one());
assert_eq!(JsBigInt::from(255).as_int_n(8).unwrap(), JsBigInt::from(-1));
assert_eq!(
JsBigInt::from(-129).as_int_n(8).unwrap(),
JsBigInt::from(127)
);
assert_eq!(
bigint("18446744073709551616").as_int_n(65).unwrap(),
bigint("-18446744073709551616")
);
assert_eq!(
bigint("36893488147419103231").as_int_n(65).unwrap(),
JsBigInt::from(-1)
);
assert_eq!(
bigint("999999999999999999999").as_int_n(0).unwrap(),
JsBigInt::zero()
);
}
#[test]
fn quickjs_2026_as_uint_n_preserves_signed_limb_quirks() {
assert_eq!(
JsBigInt::from(-1).as_uint_n(64).unwrap(),
JsBigInt::from(-1)
);
assert_eq!(
JsBigInt::from(-1).as_uint_n(128).unwrap(),
JsBigInt::from(-1)
);
assert_eq!(
bigint("9223372036854775808").as_uint_n(64).unwrap(),
JsBigInt::from(i64::MIN)
);
assert_eq!(
bigint("18446744073709551615").as_uint_n(64).unwrap(),
JsBigInt::from(-1)
);
assert_eq!(
bigint("-18446744073709551616").as_uint_n(128).unwrap(),
bigint("-18446744073709551616")
);
assert_eq!(
bigint("-18446744073709551616").as_uint_n(65).unwrap(),
bigint("18446744073709551616")
);
}
#[test]
fn as_n_preserves_the_extended_limb_allocation_gap() {
let extended = JsBigInt::one()
.shl(&JsBigInt::from(MAX_BIGINT_BITS - 1))
.unwrap();
assert_eq!(extended.signed_limb_len(), MAX_BIGINT_LIMBS + 1);
assert_eq!(
extended.as_uint_n(MAX_BIGINT_BITS - 1).unwrap(),
JsBigInt::zero()
);
assert_eq!(
extended.as_int_n(MAX_BIGINT_BITS - 1).unwrap(),
JsBigInt::zero()
);
let at_nominal_limit = extended.as_uint_n(MAX_BIGINT_BITS).unwrap();
let exact_negative = JsBigInt::normalize(-extended.to_bigint());
assert_eq!(at_nominal_limit, exact_negative);
assert_eq!(extended.as_int_n(MAX_BIGINT_BITS).unwrap(), exact_negative);
for bits in MAX_BIGINT_BITS + 1..MAX_BIGINT_BITS + BIGINT_LIMB_BITS {
assert_eq!(
extended.as_uint_n(bits),
Err(BigIntError::AllocationTooLarge)
);
assert_eq!(
extended.as_int_n(bits),
Err(BigIntError::AllocationTooLarge)
);
}
assert_eq!(
extended
.as_uint_n(MAX_BIGINT_BITS + BIGINT_LIMB_BITS)
.unwrap(),
extended
);
assert_eq!(
extended
.as_int_n(MAX_BIGINT_BITS + BIGINT_LIMB_BITS + 1)
.unwrap(),
extended
);
assert_eq!(extended.as_uint_n(u64::MAX).unwrap(), extended);
}
#[test]
fn integral_f64_conversion_preserves_the_exact_binary_value() {
assert_eq!(JsBigInt::from_integral_f64(-0.0).unwrap(), JsBigInt::zero());
assert_eq!(
JsBigInt::from_integral_f64(9_007_199_254_740_991.0).unwrap(),
bigint("9007199254740991")
);
assert_eq!(
JsBigInt::from_integral_f64(9_007_199_254_740_992.0).unwrap(),
bigint("9007199254740992")
);
assert_eq!(
JsBigInt::from_integral_f64(9_007_199_254_740_994.0).unwrap(),
bigint("9007199254740994")
);
let max_finite = JsBigInt::one()
.shl(&JsBigInt::from(1024))
.unwrap()
.sub(&JsBigInt::one().shl(&JsBigInt::from(971)).unwrap())
.unwrap();
assert_eq!(JsBigInt::from_integral_f64(f64::MAX).unwrap(), max_finite);
assert_eq!(
JsBigInt::from_integral_f64(f64::MIN).unwrap(),
max_finite.neg().unwrap()
);
assert!(JsBigInt::from_integral_f64(1.5).is_none());
assert!(JsBigInt::from_integral_f64(f64::NAN).is_none());
assert!(JsBigInt::from_integral_f64(f64::INFINITY).is_none());
assert!(JsBigInt::from_integral_f64(f64::NEG_INFINITY).is_none());
}
#[test]
fn arbitrary_precision_is_not_capped_at_i128() {
let value = JsBigInt::from(3).pow(&JsBigInt::from(1000)).unwrap();
assert!(value.magnitude_bits() > 128);
let text = value.to_string();
assert_eq!(text.len(), 478);
assert!(text.starts_with("1322070819480806636890455259752144365965"));
assert_eq!(JsBigInt::parse_js_string(&text).unwrap(), value);
}
}