moonnum.
OneIntegers

The integer that never runs out

MoonBit's BigInt is already arbitrary-precision. What it does not have is the set of operations a numeric programmer reaches for, and the spellings that make them usable. This page is those.

Division that agrees with itself

a // b rounds towards negative infinity and a % b takes the sign of b, so the two always satisfy a == (a // b) * b + a % b. Truncating division, the usual default, breaks that identity for negative numbers and quietly returns a remainder with the wrong sign.

moonbit
for (a, b) in [(7N, 2N), (-7N, 2N), (7N, -2N), (-7N, -2N)] {
  let (q, r) = @moonnum.divmod(a, b)
  println("\{a} / \{b} = \{q} rem \{r}")
}
output
7 / 2 = 3 rem 1
-7 / 2 = -4 rem 1
7 / -2 = -4 rem -1
-7 / -2 = 3 rem -1

Size is the only limit

There is no overflow to guard against, which means the interesting cases are the ones that used to need a library: factorials, binomial coefficients, integer square roots of very large numbers.

moonbit
@moonnum.factorial(50N)
@moonnum.power(2N, 200N)
@moonnum.comb(@moonnum.parse_int("100"), 50N)
@moonnum.isqrt(@moonnum.power(10N, 40N))
output
30414093201713378043612608166064768844377641568960512000000000000
1606938044258990275541962092341162602522202993782792835301376
100891344545564193334812497256
100000000000000000000

Powers, with and without a modulus

power is the modular form as well as the plain one. With a modulus and a negative exponent it computes the modular inverse, which is what the extended Euclidean algorithm is for; when no inverse exists it refuses instead of returning nonsense.

moonbit
@moonnum.power(3N, -1N, modulus=7N)
@moonnum.power(2N, 1000N, modulus=97N)
@moonnum.power(2N, -1N, modulus=4N)
output
5
36
base is not invertible for the given modulus

Without a modulus, a negative exponent has no integer result. power says so; if you want the float answer, ask Number::pow, which follows the tower.

What a refusal prints. Refused has a Show implementation, so the text above is err.to_string(): the reason, and the offset when there was one. ZeroDivision has no payload and prints as division by zero.

Reading what people write

base~ = 0 reads the prefix. Underscores are allowed between digits — not next to one — and surrounding whitespace is ignored. Everything else is refused with the offset of the character that spoiled it.

moonbit
@moonnum.parse_int("0xff_ec", base=0)
@moonnum.parse_int(" 1_000 ", base=0)
@moonnum.parse_int("0b1010", base=2)
@moonnum.parse_int("12x", base=0)
output
65516
1000
10
unexpected trailing character at offset 2

Bytes

Two's complement when you ask for it, big-endian unless you say otherwise, and an overflow when the value does not fit the length you promised.

moonbit
@moonnum.to_bytes(-2N, length=4, signed=true)
@moonnum.of_bytes(bytes, signed=true)
@moonnum.to_bytes(256N, length=1)
output
ff ff ff fe
-2
int does not fit in the requested byte length