// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// A big integer represented as an array of Int.
//
// Design explained:
// - Why use an FixedArray of Int with a len field instead of an Array[Int]?
// - It follows the principle of least dependency in MoonBit's core.
// - In our case, we always do one-off array allocation for each BigInt.
// - Why keep a separate len field instead of using limbs.length()?
// - Since we always do only once array allocation for each BigInt, we
// often need to estimate the number of limbs needed before allocating.
// Using len allows us to accommodate leading zeros.
//
// Invariants:
// - len > 0
// - forall 0 <= i < len. 0 <= limbs[i] < radix
// - (exists 0 <= i < len. limbs[i] > 0) => limbs[len-1] > 0
// - (forall 0 <= i < len. limbs[i] == 0) => limbs[0] == 0 and len == 1
// - forall len <= i < limbs.length(). limbs[i] == 0
#valtype
struct BigInt {
limbs : FixedArray[UInt] // Note: do not use limbs.length(), use len instead because of leading zeros
sign : Sign
len : Int
}
///|
priv enum Sign {
Positive
Negative
} derive(Eq, @debug.Debug)
///|
test "internal repr" {
fn convert(b : BigInt) -> @debug.Repr {
@debug.Repr::record({
"limbs": Repr(b.limbs),
"sign": Repr(b.sign),
"len": Repr(b.len),
})
}
@debug.debug_inspect(
[0N, 1, 2, 3, 4, -1, -2, -3, -4].map(convert),
content=(
#|[
#| { limbs: , sign: Positive, len: 1 },
#| { limbs: , sign: Positive, len: 1 },
#| { limbs: , sign: Positive, len: 1 },
#| { limbs: , sign: Positive, len: 1 },
#| { limbs: , sign: Positive, len: 1 },
#| { limbs: , sign: Negative, len: 1 },
#| { limbs: , sign: Negative, len: 1 },
#| { limbs: , sign: Negative, len: 1 },
#| { limbs: , sign: Negative, len: 1 },
#|]
),
)
}
// Hyper Params
///|
/// Invariants:
/// - ((RADIX - 1)^2 + (RADIX - 1)) must fit in a UInt64
/// - RADIX can only be a power of 2
/// - RADIX_BIT_LEN is multiple of 4
/// - RADIX_BIT_LEN <= 32
const RADIX_BIT_LEN = 32
///|
/// The base of the number system.
const RADIX : UInt64 = 1UL << RADIX_BIT_LEN // TODO: This can be generalized once we have const generics
///|
/// The mask to extract the lower `RADIX_BIT_LEN` bits.
const RADIX_MASK : UInt64 = RADIX - 1
///|
/// The ratio of the number of decimal digits to the number of radix digits.
const DECIMAL_RATIO : Double = 0.302 // log10(2)
///|
/// When to switch to Karatsuba multiplication
const KARATSUBA_THRESHOLD = 50
// Useful bigints
///|
let zero : BigInt = 0N
///|
let one : BigInt = 1N
// Conversion Functions
///|
/// Converts a 32-bit signed integer to a BigInt.
///
/// Parameters:
///
/// * `value` : The 32-bit signed integer (`Int`) to be converted.
///
/// Returns a `BigInt` equivalent to the input integer.
///
/// Example:
///
/// ```mbt check
/// test {
/// let big = @bigint.BigInt::from_int(42)
/// inspect(big, content="42")
/// let neg = @bigint.BigInt::from_int(-42)
/// inspect(neg, content="-42")
/// }
/// ```
pub fn BigInt::from_int(n : Int) -> BigInt {
BigInt::from_int64(n.to_int64())
}
///|
/// Converts an unsigned 32-bit integer to a `BigInt`.
///
/// Parameters:
///
/// * `value` : The unsigned 32-bit integer to be converted.
///
/// Returns a `BigInt` representing the same numerical value as the input.
///
/// Example:
///
/// ```mbt check
/// test {
/// let n = 42U
/// inspect(@bigint.BigInt::from_uint(n), content="42")
/// }
/// ```
pub fn BigInt::from_uint(n : UInt) -> BigInt {
BigInt::from_uint64(n.to_uint64())
}
///|
/// Converts a signed 64-bit integer to a `BigInt`.
///
/// Parameters:
///
/// * `number` : A 64-bit signed integer (`Int64`) to be converted.
///
/// Returns a `BigInt` value that represents the same numerical value as the
/// input.
///
/// Example:
///
/// ```mbt check
/// test {
/// let big = @bigint.BigInt::from_int64(9223372036854775807L) // max value of Int64
/// inspect(big, content="9223372036854775807")
/// let neg = @bigint.BigInt::from_int64(-9223372036854775808L) // min value of Int64
/// inspect(neg, content="-9223372036854775808")
/// }
/// ```
pub fn BigInt::from_int64(n : Int64) -> BigInt {
if n < 0L {
-BigInt::from_uint64((-n).reinterpret_as_uint64())
} else {
BigInt::from_uint64(n.reinterpret_as_uint64())
}
}
///|
/// Converts an unsigned 64-bit integer to a `BigInt`.
///
/// Parameters:
///
/// * `value` : The unsigned 64-bit integer (`UInt64`) to be converted.
///
/// Returns a new `BigInt` with the same value as the input. The resulting
/// `BigInt` will always have a positive sign since the input is an unsigned
/// integer.
///
/// Example:
///
/// ```mbt check
/// test {
/// let n = @bigint.BigInt::from_uint64(12345678901234567890UL)
/// inspect(n, content="12345678901234567890")
/// let zero = @bigint.BigInt::from_uint64(0UL)
/// inspect(zero, content="0")
/// }
/// ```
pub fn BigInt::from_uint64(n : UInt64) -> BigInt {
if n == 0UL {
return { limbs: FixedArray::make(1, 0), sign: Positive, len: 1 }
}
let limbs = FixedArray::make(64 / RADIX_BIT_LEN, 0U)
let i = for m = n, i = 0; m > 0; {
limbs[i] = (m & RADIX_MASK).to_uint()
continue m >> RADIX_BIT_LEN, i + 1
} nobreak {
i
}
{ limbs, sign: Positive, len: i }
}
// Arithmetic Operations
///|
/// Negates a big integer, returning a new big integer with the opposite sign. If
/// the input is zero, returns zero.
///
/// Parameters:
///
/// * `self` : The big integer to negate.
///
/// Returns a new big integer with the opposite sign of the input, or zero if the
/// input is zero.
///
/// Example:
///
/// ```mbt check
/// test {
/// inspect(-42N, content="-42")
/// inspect(-(-42N), content="42")
/// inspect(-0N, content="0")
/// }
/// ```
pub impl Neg for BigInt with fn neg(self : BigInt) -> BigInt {
if self.is_zero() {
return zero
}
{ ..self, sign: if self.sign == Positive { Negative } else { Positive } }
}
///|
/// Adds two arbitrary-precision integers. Handles positive and negative numbers
/// correctly by converting subtraction of negative numbers into addition of
/// positive numbers.
///
/// Parameters:
///
/// * `self` : The first big integer to add.
/// * `other` : The second big integer to add.
///
/// Returns a new `BigInt` that represents the sum of the two input numbers.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = 9223372036854775807N // Max value of Int64
/// let b = 1N
/// inspect(a + b, content="9223372036854775808") // Beyond Int64 range
/// inspect(-a + -b, content="-9223372036854775808")
/// }
/// ```
pub impl Add for BigInt with fn add(self : BigInt, other : BigInt) -> BigInt {
if self.sign == Negative {
if other.sign == Negative {
return -(-other + -self)
} else {
return other - -self
}
} else if other.sign == Negative {
return self - -other
}
let self_len = self.len
let other_len = other.len
let limbs = FixedArray::make(1 + max(self_len, other_len), 0U)
let i = for carry = 0UL, i = 0; i < self_len || i < other_len || carry != 0; {
let a = if i < self_len { self.limbs[i].to_uint64() } else { 0 }
let b = if i < other_len { other.limbs[i].to_uint64() } else { 0 }
let sum = a + b + carry
limbs[i] = (sum & RADIX_MASK).to_uint()
continue sum >> RADIX_BIT_LEN, i + 1
} nobreak {
i
}
{ limbs, sign: Positive, len: i }
}
///|
/// Subtracts one arbitrary-precision integer from another. Handles positive and
/// negative numbers appropriately.
///
/// Parameters:
///
/// * `self` : The minuend (the number to subtract from).
/// * `other` : The subtrahend (the number to be subtracted).
///
/// Returns a new `BigInt` representing the difference between `self` and
/// `other`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = 12345678901234567890N
/// let b = 9876543210987654321N
/// inspect(a - b, content="2469135690246913569")
/// inspect(-a - b, content="-22222222112222222211")
/// }
/// ```
pub impl Sub for BigInt with fn sub(self : BigInt, other : BigInt) -> BigInt {
// first make sure self and other > 0
if self.sign == Negative {
if other.sign == Negative {
return -other - -self
} else {
return -(other + -self)
}
} else if other.sign == Negative {
return self + -other
}
// then make sure self >= other
if self < other {
return -(other - self)
}
let self_len = self.len
let other_len = other.len
let limbs = FixedArray::make(max(self_len, other_len), 0U)
let i = for borrow = 0L, i = 0; i < self_len || i < other_len || borrow != 0L; {
let a = if i < self_len { self.limbs[i].to_int64() } else { 0 }
let b = if i < other_len { other.limbs[i].to_int64() } else { 0 }
let diff = a - b - borrow // 0 <= a < radix, 0 <= b < radix, 0 <= borrow <= 1 => -radix <= diff < radix
if diff < 0L {
limbs[i] = (diff + RADIX.reinterpret_as_int64())
.reinterpret_as_uint64()
.to_uint() // -radix <= diff < 0, so we don't need to mod by radix
continue 1L, i + 1
} else {
limbs[i] = diff.reinterpret_as_uint64().to_uint() // 0 <= diff < radix, so we don't need to mod by radix
continue 0L, i + 1
}
} nobreak {
i
}
// Ensure the result has at least one limb with a value of zero if the result is zero
let i = for i = i; i > 1 && limbs[i - 1] == 0; {
continue i - 1
} nobreak {
i
}
{ limbs, sign: Positive, len: i }
}
///|
/// Multiplies two arbitrary-precision integers. Uses the most efficient
/// multiplication algorithm based on the size of the operands:
///
/// * Grade school multiplication for small numbers
/// * Karatsuba multiplication for large numbers
///
/// Parameters:
///
/// * `self` : The first arbitrary-precision integer to multiply.
/// * `other` : The second arbitrary-precision integer to multiply.
///
/// Returns the product of the two numbers. The sign of the result follows the
/// standard multiplication rules: positive if both operands have the same sign,
/// negative otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = 12345678901234567890N
/// let b = -98765432109876543210N
/// inspect(a * b, content="-1219326311370217952237463801111263526900")
/// inspect(a * 0N, content="0")
/// }
/// ```
pub impl Mul for BigInt with fn mul(self : BigInt, other : BigInt) -> BigInt {
if self.is_zero() || other.is_zero() {
return zero
}
// Specialize the (n-limb) x (1-limb) case. Factorial-style chains hit
// this on every multiplication, and the general grade-school loop has
// a per-i branch on `j < other_len` plus carry propagation overhead
// that disappears here.
let ret = if other.len == 1 {
self.mul_single_limb(other.limbs.unsafe_get(0))
} else if self.len == 1 {
other.mul_single_limb(self.limbs.unsafe_get(0))
} else if self.len < KARATSUBA_THRESHOLD || other.len < KARATSUBA_THRESHOLD {
self.grade_school_mul(other)
} else {
self.karatsuba_mul(other)
}
{ ..ret, sign: if self.sign == other.sign { Positive } else { Negative } }
}
///|
/// Multiply the magnitude of `self` by a single radix-limb `x`. Returns
/// a `Positive`-signed result regardless of `self.sign` — the caller in
/// `Mul::mul` overwrites `sign` with the correct combined sign. This
/// matches the convention of the other magnitude-only multiply helpers
/// (`grade_school_mul`, `karatsuba_mul`). Do not call directly when a
/// signed product is needed.
///
/// Requires `self` to be non-zero and `x > 0`: with a zero operand the
/// result would be an all-zero magnitude with `len > 1`, breaking the
/// no-leading-zeros invariant. The call site in `Mul::mul` guarantees
/// this via its `is_zero` guard.
fn BigInt::mul_single_limb(self : BigInt, x : UInt) -> BigInt {
let n = self.len
let limbs = FixedArray::make(n + 1, 0U)
let x_u64 = x.to_uint64()
let mut carry = 0UL
for i in 0..> RADIX_BIT_LEN
}
let len = if carry == 0UL {
n
} else {
limbs[n] = carry.to_uint()
n + 1
}
{ limbs, sign: Positive, len }
}
// Simplest way to multiply two BigInts.
///|
fn BigInt::grade_school_mul(self : BigInt, other : BigInt) -> BigInt {
let self_len = self.len
let other_len = other.len
let mut len = self_len + other_len
let limbs = FixedArray::make(len, 0U)
for i in 0..> RADIX_BIT_LEN
}
}
if limbs[self_len + other_len - 1] == 0 {
len -= 1
}
{ limbs, sign: Positive, len }
}
// Karatsuba multiplication
///|
fn BigInt::karatsuba_mul(self : BigInt, other : BigInt) -> BigInt {
let half = (max(self.len, other.len) + 1) / 2
let (xl, xh) = self.split(half)
let (yl, yh) = other.split(half)
let p1 = xh * yh
let p2 = xl * yl
let p3 = (xh + xl) * (yh + yl)
(p1 << (RADIX_BIT_LEN * 2 * half)) +
((p3 - p1 - p2) << (RADIX_BIT_LEN * half)) +
p2
}
// Get the lower half of the number.
///|
fn BigInt::split(self : BigInt, half : Int) -> (BigInt, BigInt) {
if self.len <= half {
return ({ ..self, sign: Positive }, zero)
}
let lower_len = for i in half>..1 {
if self.limbs[i] > 0 {
break i + 1
}
} nobreak {
1
}
let lower = FixedArray::make(lower_len, 0U)
lower.unsafe_blit(0, self.limbs, 0, lower_len)
let upper = FixedArray::make(self.len - half, 0U)
upper.unsafe_blit(0, self.limbs, half, self.len - half)
(
{ limbs: lower, sign: Positive, len: lower_len },
{ limbs: upper, sign: Positive, len: self.len - half },
)
}
///|
/// Performs division between two arbitrary-precision integers, following
/// standard arithmetic rules for signed division.
///
/// Parameters:
///
/// * `self` : The dividend big integer.
/// * `other` : The divisor big integer.
///
/// Returns the quotient of the division.
///
/// Throws a panic if the divisor is zero.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = @bigint.BigInt::from_string("100")
/// let b = @bigint.BigInt::from_string("20")
/// inspect(a / b, content="5")
/// inspect(-a / b, content="-5")
/// inspect(a / -b, content="-5")
/// inspect(-a / -b, content="5")
/// }
/// ```
pub impl Div for BigInt with fn div(self : BigInt, other : BigInt) -> BigInt {
if other is 0 {
abort("division by zero")
}
// Handle negative numbers
if self.sign == Negative {
if other.sign == Negative {
BigInt::grade_school_div(-self, -other).0
} else {
-BigInt::grade_school_div(-self, other).0
}
} else if other.sign == Negative {
-BigInt::grade_school_div(self, -other).0
} else {
return BigInt::grade_school_div(self, other).0
}
}
///|
/// Calculates the modulo (remainder) of dividing one big integer by another.
///
/// Parameters:
///
/// * `self` : The dividend big integer.
/// * `other` : The divisor big integer.
///
/// Returns the remainder of the division operation.
///
/// Throws an error if `other` is zero.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = 42N
/// let b = 5N
/// inspect(a % b, content="2")
/// let c = -42N
/// let d = -5N
/// inspect(c % d, content="-2")
/// }
/// ```
pub impl Mod for BigInt with fn mod(self : BigInt, other : BigInt) -> BigInt {
if other == zero {
abort("division by zero")
}
// Handle negative numbers
if self.sign == Negative {
if other.sign == Negative {
-BigInt::grade_school_div(-self, -other).1
} else {
-BigInt::grade_school_div(-self, other).1
}
} else if other.sign == Negative {
BigInt::grade_school_div(self, -other).1
} else {
BigInt::grade_school_div(self, other).1
}
}
// Simplest way to divide two BigInts.
// Assumption: other != zero.
///|
fn BigInt::grade_school_div(self : BigInt, other : BigInt) -> (BigInt, BigInt) {
// Handle edge cases
if self < other {
return (zero, self)
} else if self == other {
return (one, zero)
}
if other.len == 1 {
let number = other.limbs[0]
let ret = self.copy()
if number == 1 {
return (ret, zero)
}
let a = ret.limbs
let x = number.to_uint64()
let y = for i in self.len>..0; y = 0UL {
let y = y << RADIX_BIT_LEN
let y = y + a[i].to_uint64()
a[i] = ((y / x) & RADIX_MASK).to_uint()
continue y % x
} nobreak {
y
}
if ret.limbs[ret.len - 1] == 0 {
return (
{ ..ret, len: ret.len - 1 },
{ limbs: FixedArray::make(1, y.to_uint()), sign: Positive, len: 1 },
)
}
return (
ret,
{ limbs: FixedArray::make(1, y.to_uint()), sign: Positive, len: 1 },
)
}
// Cite: TAOCP Vol. 2, 4.3.1
let dividend = self
let divisor = other
// D1. normalize
// m = dividend.len - divisor.len
// left shift dividend & divisor such that
// - b[b.length() - 1] >= radix / 2
// - a.length() == self.len + 1
// where a and b represent the limbs of the adjusted dividend and divisor
let lshift = max(
0,
RADIX_BIT_LEN - (64 - divisor.limbs[divisor.len - 1].to_int64().clz()),
)
let a_len = dividend.len
let dividend = dividend << lshift
let divisor = divisor << lshift
let b_len = divisor.len
let b = FixedArray::make(b_len, 0UL)
for i in 0.. a_len {
a[a_len] = dividend.limbs[a_len].to_uint64()
}
// invariant : divisor.limbs.last() >= radix / 2
// if b[b_len - 1] < radix / 2 {
// panic()
// }
let a_len = a_len + 1
// a is the adjusted dividend and b is the adjusted divisor
let v1 = b[b_len - 1]
let v2 = b[b_len - 2]
let q = FixedArray::make(a_len - b_len, 0U)
// D2 - D7 loop through m to 0
for i in q.length()>..0 {
let u0 = a[i + b_len]
let u1 = a[i + b_len - 1]
let u2 = a[i + b_len - 2]
// D3 compute qh
let mut qh = (u0 * RADIX + u1) / v1
if qh * v2 > RADIX * (u0 * RADIX + u1 - qh * v1) + u2 {
qh -= 1
}
// D4 divident = divident - qh * divisor
let mut borrow = 0L
let mut carry = 0UL
for j in 0..> RADIX_BIT_LEN
carry = carry >> RADIX_BIT_LEN
}
borrow += a[i + b_len].reinterpret_as_int64()
borrow -= carry.reinterpret_as_int64()
a[i + b_len] = (borrow & RADIX_MASK.reinterpret_as_int64()).reinterpret_as_uint64()
borrow = borrow >> RADIX_BIT_LEN
if borrow < 0L {
carry = 0UL
for j in 0..> RADIX_BIT_LEN
}
carry += a[i + b_len]
a[i + b_len] = carry & RADIX_MASK
carry = carry >> RADIX_BIT_LEN
borrow += carry.reinterpret_as_int64()
qh -= 1
}
q[i] = qh.to_uint()
}
let len = if q[q.length() - 1] == 0 { q.length() - 1 } else { q.length() }
// strip leading zeros
let mut i = a.length() - 1
while i >= 0 && a[i] == 0 {
i -= 1
}
if i < 0 {
i = 1
} else {
i += 1
}
let modulo = FixedArray::make(i, 0U)
for j in 0..> lshift)
}
// Bitwise Operations
///|
/// Performs a left shift operation on a `BigInt` value. Preserves the sign of
/// the original number while shifting only its absolute value.
///
/// Parameters:
///
/// * `self` : The `BigInt` value to be shifted.
/// * `n` : The number of positions to shift left. Must be non-negative.
///
/// Returns a new `BigInt` value that is the result of shifting the absolute
/// value of the input left by `n` positions, maintaining the original sign.
///
/// Throws a panic if the shift count is negative.
///
/// Example:
///
/// ```mbt check
/// test {
/// let x = 5N
/// inspect(x << 2, content="20")
/// let y = -5N
/// inspect(y << 2, content="-20")
/// }
/// ```
pub impl Shl for BigInt with fn shl(self : BigInt, n : Int) -> BigInt {
if n < 0 {
abort("negative shift count")
}
if !self.is_zero() {
let new_limbs = FixedArray::make(
self.len + (n + RADIX_BIT_LEN - 1) / RADIX_BIT_LEN, // ceiling(n / RADIX_BIT_LEN)
0U,
)
let a = self.limbs
let r = n % RADIX_BIT_LEN
let lz = n / RADIX_BIT_LEN // number of leading zeros
let mut len = self.len + lz
if r != 0 {
let carry = for i in 0..> RADIX_BIT_LEN
} nobreak {
carry
}
if carry != 0 {
new_limbs[self.len + lz] = carry.to_uint()
len += 1
}
} else {
new_limbs.unsafe_blit(lz, self.limbs, 0, self.len)
}
{ limbs: new_limbs, sign: self.sign, len }
} else {
zero
}
}
///|
/// Performs arithmetic right shift operation on a big integer value. The shift
/// operation preserves the sign of the number while shifting the absolute value
/// right by `n` bits. For negative numbers, the result is rounded towards
/// negative infinity.
///
/// Parameters:
///
/// * `self` : The big integer value to be shifted.
/// * `n` : The number of bits to shift right. Must be non-negative.
///
/// Returns a new `BigInt` value that represents the result of shifting `self`
/// right by `n` bits.
///
/// Throws a panic if `n` is negative.
///
/// Example:
///
/// ```mbt check
/// test {
/// let n = @bigint.BigInt::from_string("1024")
/// inspect(n >> 3, content="128")
/// let neg = @bigint.BigInt::from_string("-1024")
/// inspect(neg >> 3, content="-128")
/// }
/// ```
pub impl Shr for BigInt with fn shr(self : BigInt, n : Int) -> BigInt {
if n < 0 {
abort("negative shift count")
}
let r = n % RADIX_BIT_LEN
let lz = n / RADIX_BIT_LEN
if lz >= self.len {
match self.sign {
Positive => return zero
Negative =>
return { limbs: FixedArray::make(1, 1), sign: Negative, len: 1 }
}
}
let mut new_len = self.len - lz
if r == 0 {
let new_limbs = FixedArray::make(new_len, 0U)
new_limbs.unsafe_blit(0, self.limbs, lz, new_len)
let result = { limbs: new_limbs, sign: self.sign, len: new_len }
if self.sign == Negative {
for i in 0....lz; carry = 0UL {
let x = a[i].to_uint64()
new_limbs[i - lz] = ((x >> r) | carry).to_uint()
continue (x << (RADIX_BIT_LEN - r)) & RADIX_MASK
} nobreak {
carry
}
if new_len > 1 && new_limbs[new_len - 1] == 0 {
new_len -= 1
}
if self.sign == Negative {
let mut has_remainder = carry != 0UL
if !has_remainder {
for i in 0.. Bool {
self.len == 1 && self.limbs[0] == 0
}
///|
/// Compares two arbitrary-precision integers and returns their relative order.
///
/// Parameters:
///
/// * `self` : The first arbitrary-precision integer to compare.
/// * `other` : The second arbitrary-precision integer to compare.
///
/// Returns an integer indicating the relative order:
///
/// * A negative value if `self` is less than `other`
/// * Zero if `self` equals `other`
/// * A positive value if `self` is greater than `other`
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = @bigint.BigInt::from_string("42")
/// let b = @bigint.BigInt::from_string("24")
/// let c = @bigint.BigInt::from_string("-42")
/// inspect(a.compare(b), content="1") // 42 > 24
/// inspect(b.compare(a), content="-1") // 24 < 42
/// inspect(c.compare(a), content="-1") // -42 < 42
/// inspect(a.compare(a), content="0") // 42 = 42
/// }
/// ```
pub impl Compare for BigInt with fn compare(self, other) {
if self.sign != other.sign {
return if self.sign == Positive { 1 } else { -1 }
}
let self_len = self.len
let other_len = other.len
if self_len != other_len {
return if self.sign == Positive {
self_len - other_len
} else {
other_len - self_len
}
}
for i in self_len>..0 {
if self.limbs[i] != other.limbs[i] {
return if self.sign == Positive {
self.limbs[i].compare(other.limbs[i])
} else {
other.limbs[i].compare(self.limbs[i])
}
}
}
0
}
///|
/// Compares two `BigInt` values for equality. Returns true if both numbers have
/// the same sign and magnitude.
///
/// Parameters:
///
/// * `self` : The first `BigInt` value to compare.
/// * `other` : The second `BigInt` value to compare.
///
/// Returns `true` if the two `BigInt` values are equal, `false` otherwise.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = 123456789N
/// let b = 123456789N
/// let c = -123456789N
/// inspect(a == b, content="true")
/// inspect(a == c, content="false")
/// }
/// ```
pub impl Eq for BigInt with fn equal(self, other) {
if self.sign != other.sign || self.len != other.len {
return false
}
for i in 0.. String {
if radix < 2 || radix > 36 {
abort("radix must be between 2 and 36")
}
if radix != 10 {
return self.to_string_radix(radix)
}
// This function first converts the BigInt to a decimal representation, with a radix of 2^(`decimal_radix_bit_len`).
// Then it converts the decimal representation to a string slot by slot.
if self.is_zero() {
return "0"
}
let decimal_radix_bit_len = 19 - 1 - (1 + RADIX_BIT_LEN) / 3 // < len(9,223,372,036,854,775,807) - len(2^RADIX_BIT_LEN). len means the number of digits in decimal.
let decimal_mask = 10_000_000L // 10^(decimal_radix_bit_len). TODO: compute it when we have power function.
// The following value should fit well into an Int without precision loss.
// This is an approximation of the number of slots needed to represent the decimal value.
let decimal_len = unchecked_double_to_int(
(self.len * RADIX_BIT_LEN).to_double() *
DECIMAL_RATIO /
decimal_radix_bit_len.to_double() +
1,
)
let v = Array::make(decimal_len, 0L)
let mut v_idx = 0
for i in self.len>..0 {
let mut x = self.limbs[i].to_int64()
for j in 0.. 0L {
v[v_idx] = x % decimal_mask
v_idx += 1
x /= decimal_mask
}
}
// Materialize the decimal digits into a pre-sized buffer, filling from the
// least-significant end, so the result is built with a single allocation
// instead of the O(n^2) prepending string concatenations this used to do.
let cap = v_idx * decimal_radix_bit_len + 1 // +1 for an optional sign
let chars = FixedArray::make(cap, '0')
let mut pos = cap
// Lower slots each contribute exactly `decimal_radix_bit_len` digits,
// zero-padded (the most-significant slot is handled separately to avoid leading zeros).
for i in 0..<(v_idx - 1) {
let mut x = v[i]
for _ in 0..= 1`
// and the top slot is non-zero (guarded by the is_zero() check above).
for x = v[v_idx - 1]; x > 0L; x = x / 10L {
pos -= 1
chars[pos] = char_from_digit((x % 10L).to_int())
}
if self.sign == Negative {
pos -= 1
chars[pos] = '-'
}
String::from_array(chars[pos:])
}
///|
fn digit_from_char(x : Int) -> Int {
match x {
'0'..='9' => x - '0'
'A'..='Z' => x + (10 - 'A')
'a'..='z' => x + (10 - 'a')
_ => -1
}
}
///|
fn char_from_digit(d : Int) -> Char {
if d < 10 {
(d + '0').unsafe_to_char()
} else {
(d - 10 + 'a').unsafe_to_char()
}
}
///|
fn pow2_shift(radix : Int) -> Int? {
if radix >= 2 && (radix & (radix - 1)) == 0 {
Some(radix.ctz())
} else {
None
}
}
///|
fn BigInt::to_string_radix(self : BigInt, radix : Int) -> String {
if self.is_zero() {
return "0"
}
match pow2_shift(radix) {
Some(shift) => self.to_string_radix_pow2(shift)
None => {
let is_negative = self.sign == Negative
let base = BigInt::from_int(radix)
let value = if is_negative { -self } else { self }
let digits = []
for v = value {
if v > zero {
let (q, r) = BigInt::grade_school_div(v, base)
digits.push(char_from_digit(r.to_int()))
continue q
} else {
break
}
}
let builder = StringBuilder(
size_hint=digits.length() + (if is_negative { 1 } else { 0 }),
)
if is_negative {
builder.write_char('-')
}
for i in digits.length()>..0 {
builder.write_char(digits[i])
}
builder.to_string()
}
}
}
///|
fn BigInt::to_string_radix_pow2(self : BigInt, shift : Int) -> String {
let is_negative = self.sign == Negative
let value = if is_negative { -self } else { self }
let bit_len = value.bit_length()
let digit_len = (bit_len + shift - 1) / shift
let builder = StringBuilder(
size_hint=digit_len + (if is_negative { 1 } else { 0 }),
)
if is_negative {
builder.write_char('-')
}
let mask = (1UL << shift) - 1UL
for pos in digit_len>..0 {
let bit_index = pos * shift
let limb_index = bit_index / RADIX_BIT_LEN
let offset = bit_index % RADIX_BIT_LEN
let mut chunk = value.limbs[limb_index].to_uint64() >> offset
if offset + shift > RADIX_BIT_LEN && limb_index + 1 < value.len {
chunk = chunk |
(value.limbs[limb_index + 1].to_uint64() << (RADIX_BIT_LEN - offset))
}
let digit = (chunk & mask).to_int()
builder.write_char(char_from_digit(digit))
}
builder.to_string()
}
///|
fn BigInt::from_string_radix(input : StringView, radix : Int) -> BigInt raise {
match pow2_shift(radix) {
Some(shift) => BigInt::from_string_radix_pow2(input, radix, shift)
None => {
let len = input.length()
if len == 0 {
syntax_err()
}
let sign : Sign = if input.unsafe_get(0) == '-' {
Negative
} else {
Positive
}
let start = if sign == Negative { 1 } else { 0 }
if start == len {
syntax_err()
}
let base = BigInt::from_int(radix)
let acc = for i in start..= radix {
syntax_err()
}
continue acc * base + BigInt::from_int(digit)
} nobreak {
acc
}
if sign == Negative {
-acc
} else {
acc
}
}
}
}
///|
fn BigInt::from_string_radix_pow2(
input : StringView,
radix : Int,
shift : Int,
) -> BigInt raise {
let len = input.length()
if len == 0 {
syntax_err()
}
let sign : Sign = if input.unsafe_get(0) == '-' { Negative } else { Positive }
let start = if sign == Negative { 1 } else { 0 }
if start == len {
syntax_err()
}
// Skip leading zeros; `first` is the index of the first significant
// digit. Carried as a functional loop variable rather than a mutable
// local, yielded via `break first`.
let first = for first = start {
if first >= len {
break first
}
let digit = digit_from_char(input.unsafe_get(first).to_int())
if digit < 0 || digit >= radix {
syntax_err()
}
if digit != 0 {
break first
}
continue first + 1
}
if first == len {
return zero
}
let digits_count = len - first
let total_bits = digits_count * shift
let b_len = (total_bits + RADIX_BIT_LEN - 1) / RADIX_BIT_LEN
let limbs = FixedArray::make(b_len, 0U)
for i in len>..first; bit_pos = 0 {
let digit = digit_from_char(input.unsafe_get(i).to_int())
if digit < 0 || digit >= radix {
syntax_err()
}
let limb_index = bit_pos / RADIX_BIT_LEN
let offset = bit_pos % RADIX_BIT_LEN
let val = digit.reinterpret_as_uint().to_uint64()
limbs[limb_index] = (limbs[limb_index].to_uint64() | (val << offset)).to_uint()
if offset + shift > RADIX_BIT_LEN {
let hi = val >> (RADIX_BIT_LEN - offset)
limbs[limb_index + 1] = (limbs[limb_index + 1].to_uint64() | hi).to_uint()
}
continue bit_pos + shift
}
let b_len = normalize_len(limbs, b_len)
let sign = if b_len == 1 && limbs[0] == 0 { Positive } else { sign }
{ limbs, sign, len: b_len }
}
///|
/// Parses a string into a `BigInt` using the specified base (2 to 36).
///
/// Returns an error if the input is malformed or the base is invalid.
#internal(internal, "use `@string.parse_bigint` instead")
#doc(hidden)
pub fn parse_bigint(str : StringView, base? : Int = 10) -> BigInt raise {
if base < 2 || base > 36 {
base_err()
}
if base != 10 {
return BigInt::from_string_radix(str, base)
}
BigInt::from_string_dec(str)
}
///|
/// Converts a string representation in the specified radix to a BigInt value.
///
/// Panics if the input is malformed. Use `@string.parse_bigint` to handle errors.
pub fn BigInt::from_string(input : String, radix? : Int = 10) -> BigInt {
parse_bigint(input.view(), base=radix) catch {
Failure(msg) => abort(msg)
_ => abort("invalid syntax")
}
}
///|
fn BigInt::from_string_dec(input : StringView) -> BigInt raise {
let len = input.length()
if len == 0 {
syntax_err()
}
let sign : Sign = if input.unsafe_get(0) == '-' { Negative } else { Positive }
if sign == Negative && len == 1 {
syntax_err()
}
let mut b_len = (
unchecked_double_to_int(len.to_double() / DECIMAL_RATIO) +
1 +
RADIX_BIT_LEN
) /
RADIX_BIT_LEN +
1
let b = FixedArray::make(b_len, 0U)
for
i in (match sign {
Negative => 1
Positive => 0
}).. 9 {
syntax_err()
}
let mut carry = x.reinterpret_as_uint().to_uint64()
for j in 0..> RADIX_BIT_LEN
}
}
while b[b_len - 1] == 0 && b_len > 1 {
b_len -= 1
}
let sign = if b_len == 1 && b[0] == 0 { Positive } else { sign }
{ limbs: b, sign, len: b_len }
}
///|
fn BigInt::copy(self : BigInt) -> BigInt {
let new_limbs = FixedArray::make(self.len, 0U)
new_limbs.unsafe_blit(0, self.limbs, 0, self.len)
{ limbs: new_limbs, sign: self.sign, len: self.len }
}
///|
/// Normalizes the len field by finding the highest non-zero limb.
/// Ensures that the BigInt invariant is maintained: if all limbs are 0, len must be 1.
fn normalize_len(limbs : FixedArray[UInt], max_len : Int) -> Int {
let mut actual_len = max_len
while actual_len > 1 && limbs[actual_len - 1] == 0U {
actual_len -= 1
}
actual_len
}
///|
fn[T : Compare] max(a : T, b : T) -> T {
if a > b {
a
} else {
b
}
}
///|
/// Computes the result of raising a `BigInt` to the power of another `BigInt`,
/// with an optional modulus.
///
/// When a modulus is provided, computes the modular exponentiation using the
/// square-and-multiply algorithm. This is particularly useful in cryptographic
/// applications where direct exponentiation would result in numbers too large to
/// handle efficiently.
///
/// Parameters:
///
/// * `self` : The base number to be raised to a power.
/// * `exp` : The exponent (must be non-negative).
/// * `modulus` : Optional modulus for modular exponentiation (must be positive
/// if provided).
///
/// Returns the result of the exponentiation, or the result modulo `modulus` if a
/// modulus is provided.
///
/// Throws:
///
/// * Aborts if the exponent is negative.
/// * Aborts if the provided modulus is zero or negative.
///
/// Example:
///
/// ```mbt check
/// test {
/// let base = @bigint.BigInt::from_string("3")
/// let exp = @bigint.BigInt::from_string("4")
/// inspect(base.pow(exp), content="81")
/// inspect(base.pow(exp, modulus=@bigint.BigInt::from_string("10")), content="1")
/// }
/// ```
pub fn BigInt::pow(self : BigInt, exp : BigInt, modulus? : BigInt) -> BigInt {
if exp.sign == Negative {
abort("negative exponent")
}
match modulus {
None => {
let mut result = 1N
let mut base = self
let mut exp = exp
while exp > 0 {
if exp % 2 == 1 {
result *= base
}
base *= base
exp /= 2
}
result
}
Some(modulus) => {
guard! !(modulus.is_zero() || modulus.sign == Negative)
let mut result = 1N % modulus
let mut base = (self % modulus + modulus) % modulus
let mut exp = exp
while exp > 0 {
if exp % 2 == 1 {
result = result * base % modulus
}
base = base * base % modulus
exp /= 2
}
result
}
}
}
///|
/// Converts a big-endian byte sequence to a `BigInt` value with an optional
/// sign. Interprets the input bytes as a big-endian representation of an
/// unsigned integer, and applies the specified sign to create the final `BigInt`
/// value.
///
/// Parameters:
///
/// * `bytes` : A sequence of bytes representing the magnitude of the number in
/// big-endian order. The sequence must not be empty unless `sign` is 0.
/// * `sign` : An integer specifying the sign of the resulting number (default:
/// 1). A value of 1 creates a positive number, -1 creates a negative number, and
/// 0 returns zero regardless of the input bytes.
///
/// Returns a `BigInt` value representing the number encoded in the byte sequence
/// with the specified sign.
///
/// Throws a panic if the input byte sequence is empty and the sign is not 0.
///
/// Example:
///
/// ```mbt check
/// test {
/// let bytes = b"\x01\x02\x03" // Represents 0x010203
/// let positive = @bigint.BigInt::from_octets(bytes)
/// let negative = @bigint.BigInt::from_octets(bytes, signum=-1)
/// inspect(positive, content="66051")
/// inspect(negative, content="-66051")
/// }
/// ```
pub fn BigInt::from_octets(input : BytesView, signum? : Int = 1) -> BigInt {
let len = input.length() // number of bytes
if signum == 0 {
return zero
} else if signum < 0 {
return -BigInt::from_octets(input)
}
if len == 0 {
abort("empty octet string")
}
let div = len * 8 / RADIX_BIT_LEN
let mod = len * 8 % RADIX_BIT_LEN // number of bits in the first limb
let limbs_len = if mod == 0 { div } else { div + 1 }
let limbs = FixedArray::make(limbs_len, 0U)
// head at most significant limb
for i in 0..<(mod / 8) {
limbs[limbs_len - 1] = (limbs[limbs_len - 1] << 8) | input[i].to_uint()
}
let byte_per_limb = RADIX_BIT_LEN / 8
// tail
for i in 0.. Bytes {
let length = match length {
None => 1
Some(l) => if l <= 0 { abort("negative length") } else { l }
}
if self.is_zero() {
return Bytes::new(max(1, length))
}
if self.sign == Negative {
abort("negative BigInt")
}
let head_bits = 32 - self.limbs[self.len - 1].reinterpret_as_int().clz()
let tail_len = self.len - 1
let len = (head_bits + 7) / 8 + tail_len * (RADIX_BIT_LEN / 8)
let len = max(length, len)
let result = FixedArray::make(len, b'\x00')
for i in 0..= self.len {
break
}
result[len - 1 - i] = ((self.limbs[i / 4] >> (i % 4 * 8)) & 0xffU)
.reinterpret_as_int()
.to_byte()
}
unsafe_fixedarray_to_bytes(result)
}
///|
/// Performs a bitwise AND operation between two arbitrary-precision integers.
///
/// The operation is performed using two's complement representation, which means
/// it handles both positive and negative numbers correctly. For negative
/// numbers, the function first converts them to their two's complement form,
/// performs the AND operation, and then converts the result back if necessary.
///
/// Parameters:
///
/// * `self` : The first arbitrary-precision integer operand.
/// * `other` : The second arbitrary-precision integer operand.
///
/// Returns a new `BigInt` representing the result of the bitwise AND operation.
///
/// Example:
///
/// ```mbt check
/// test {
/// let a = @bigint.BigInt::from_string("42") // 0b101010
/// let b = @bigint.BigInt::from_string("-12") // ~0b1011 + 1
/// inspect(a & b, content="32") // 0b100000
/// let a = @bigint.BigInt::from_string("-8") // ~0b111 + 1
/// let b = @bigint.BigInt::from_string("-4") // ~0b11 + 1
/// inspect(a & b, content="-8") // ~0b1011 + 1
/// }
/// ```
pub impl BitAnd for BigInt with fn land(self : BigInt, other : BigInt) -> BigInt {
let max_length = if self.limbs.length() < other.limbs.length() {
other.limbs.length() + 1
} else {
self.limbs.length() + 1
}
// Extend the limbs to store the sign bits
let x_limbs = FixedArray::make(max_length, 0U)
x_limbs.unsafe_blit(0, self.limbs, 0, self.limbs.length())
let y_limbs = FixedArray::make(max_length, 0U)
y_limbs.unsafe_blit(0, other.limbs, 0, other.limbs.length())
// Calculate the complement code per 2
if self.sign == Negative {
for i in 0.. BigInt {
let max_length = if self.limbs.length() < other.limbs.length() {
other.limbs.length() + 1
} else {
self.limbs.length() + 1
}
// Extend the limbs to store the sign bits
let x_limbs = FixedArray::make(max_length, 0U)
x_limbs.unsafe_blit(0, self.limbs, 0, self.limbs.length())
let y_limbs = FixedArray::make(max_length, 0U)
y_limbs.unsafe_blit(0, other.limbs, 0, other.limbs.length())
// Calculate the complement code per 2
if self.sign == Negative {
for i in 0.. BigInt {
let max_length = if self.limbs.length() < other.limbs.length() {
other.limbs.length() + 1
} else {
self.limbs.length() + 1
}
// Extend the limbs to store the sign bits
let x_limbs = FixedArray::make(max_length, 0U)
x_limbs.unsafe_blit(0, self.limbs, 0, self.limbs.length())
let y_limbs = FixedArray::make(max_length, 0U)
y_limbs.unsafe_blit(0, other.limbs, 0, other.limbs.length())
// Calculate the complement code per 2
if self.sign == Negative {
for i in 0.. Int {
self.to_uint().reinterpret_as_int()
}
///|
/// Converts a `BigInt` to an unsigned 32-bit integer (`UInt`).
///
/// Parameters:
///
/// * `self` : The `BigInt` value to be converted.
///
/// Returns a `UInt` value representing the lower 32 bits of the input `BigInt`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let n = 42N
/// inspect(n.to_uint(), content="42")
/// let neg = -1N
/// inspect(neg.to_uint(), content="4294967295") // 2^32 - 1
/// }
/// ```
pub fn BigInt::to_uint(self : BigInt) -> UInt {
let value = if self.sign == Negative { (1N << 32) + self } else { self }
value.limbs[0]
}
///|
/// Converts a `BigInt` to a signed 64-bit integer (`Int64`).
///
/// Parameters:
///
/// * `value` : The `BigInt` value to be converted.
///
/// Returns a 64-bit signed integer (`Int64`) representing the lower 64 bits of
/// the input `BigInt`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let big = 9223372036854775807N // max value of Int64
/// inspect(big.to_int64(), content="9223372036854775807")
/// let bigger = big + 1
/// inspect(bigger.to_int64(), content="-9223372036854775808") // Overflow to Int64.min_value
/// }
/// ```
pub fn BigInt::to_int64(self : BigInt) -> Int64 {
self.to_uint64().reinterpret_as_int64()
}
///|
/// Converts a `BigInt` to an unsigned 64-bit integer (`UInt64`).
///
/// Parameters:
///
/// * `self` : The `BigInt` value to be converted.
///
/// Returns a `UInt64` value representing the lower 64 bits of the input
/// `BigInt`.
///
/// Example:
///
/// ```mbt check
/// test {
/// let n = 12345678901234567890N
/// inspect(n.to_uint64(), content="12345678901234567890")
/// let neg = -1N
/// inspect(neg.to_uint64(), content="18446744073709551615") // 2^64 - 1
/// }
/// ```
pub fn BigInt::to_uint64(self : BigInt) -> UInt64 {
let value = if self.sign == Negative { (1N << 64) + self } else { self }
let len = 64 / RADIX_BIT_LEN
let len = if value.len < len { value.len } else { len }
let mut result = 0UL
for i in len>..0 {
result = result << RADIX_BIT_LEN
result = result | (value.limbs[i].to_uint64() & RADIX_MASK)
}
result
}
///|
/// Returns the number of bits required to represent a `BigInt` value in two's
/// complement format, excluding the sign bit.
///
/// Parameters:
///
/// * `self` : The `BigInt` value whose bit length is to be calculated.
///
/// Example:
///
/// ```mbt check
/// test {
/// let pos = 16N // 10000
/// inspect(pos.bit_length(), content="5")
/// let neg = -16N //
/// inspect(neg.bit_length(), content="4")
/// let zero = 0N
/// inspect(zero.bit_length(), content="0")
/// }
/// ```
pub fn BigInt::bit_length(self : BigInt) -> Int {
if self.len == 0 {
return 0
}
let mut bit_length = (self.len - 1) * RADIX_BIT_LEN +
(RADIX_BIT_LEN - self.limbs[self.len - 1].clz())
if self.sign == Negative {
// check if this number is a power of two
let mut total_bits = 0
for i in 0.. Int {
if self.is_zero() {
return 0
}
// Find first non-zero limb
let i = for i = 0; i < self.len && self.limbs[i] == 0; {
continue i + 1
} nobreak {
i
}
RADIX_BIT_LEN * i + self.limbs[i].ctz()
}
///|
fn unchecked_double_to_int(d : Double) -> Int = "%f64_to_i32"
///|
fn unsafe_fixedarray_to_bytes(arr : FixedArray[Byte]) -> Bytes = "%identity"
///|
fn can_convert_to_int(x : BigInt) -> Bool {
// bigint range from [-(2^32 - 1), 2^32 - 1] has len == 1. But here we only
// want bigint from [-2^31, 2^31 - 1]
x.len == 1 &&
(if x.sign == Negative {
x.limbs[0] <= 0x80000000
} else {
x.limbs[0] < 0x80000000
})
}
///|
fn can_convert_to_int64(x : BigInt) -> Bool {
if x.len == 1 {
true
} else if x.len == 2 {
if x.sign == Negative {
x.limbs[1] < 0x80000000 || (x.limbs[1] == 0x80000000 && x.limbs[0] == 0)
} else {
x.limbs[1] < 0x80000000
}
} else {
false
}
}
///|
fn is_neg(x : BigInt) -> Bool {
x.sign == Negative
}
///|
test "limb length" {
inspect(0N.len, content="1")
inspect(1N.len, content="1")
inspect((-1N).len, content="1")
inspect(2147483647N.len, content="1") // Int.max_value
inspect((-2147483648N).len, content="1") // Int.min_value
inspect(2147483648N.len, content="1") // Int.max_value + 1
inspect((-2147483649N).len, content="1") // Int.min_value - 1
inspect(4294967295N.len, content="1") // 2^32 - 1
inspect((-4294967295N).len, content="1") // -(2^32 - 1)
inspect(4294967296N.len, content="2") // 2^32
inspect((-4294967296N).len, content="2") // -2^32
}
///|
fn BigInt::limbs(self : Self) -> Array[UInt] {
self.limbs[:self.len].to_owned()
}