Arbitrary-precision numbers for Mojo¶
APN Mojo brings arbitrary-precision arithmetic and array programming together in one pure Mojo library. It includes exact integers and fractions, correctly rounded floating-point and complex arithmetic, interval arithmetic with guaranteed bounds, and multidimensional arrays of these number types. Write scalar functions once, then reuse them across arrays and build reductions, accumulations, and outer products with its vectorization tools.
All of this runs in Mojo, without a runtime dependency on GMP, MPFR, MPC, or Python. APN Mojo supports Linux and macOS with Mojo 1.1.0.
Exact whole numbers, with bit operations, integer roots, modular arithmetic, primality tests, and combinatorics.
Fractions in lowest terms, including exact decimal input such as "0.1".
Choose a binary precision, exponent range, rounding mode, and error traps. Each arithmetic operation rounds once to the chosen format.
Choose exact rational components with ExactComplex, or independently
rounded Float components with Complex, including elementary functions.
Carry real intervals or complex rectangles through calculations, and find out what their guaranteed bounds establish.
Store multidimensional arrays, select elements with masks, and map scalar functions across their values.
sum and dot keep intermediates exact and round once. lift builds
reductions, accumulations, and outer products from binary functions.
First program¶
After installing the package or a checkout, run this
program from the directory containing pixi.toml. From a checkout:
pixi run --locked mojo run -I src docs/examples/quickstart.mojo
With the apn_mojo package installed, save it as quickstart.mojo and run
pixi run mojo run quickstart.mojo, without -I src.
"""Exact integers and fractions, Floats of any precision and Complex numbers."""
from apn_mojo import ArithmeticContext, Complex, Float, FloatFormat, Integer, Rational, sqrt
def main() raises:
# Integers and Rationals are exact: they grow instead of overflowing or rounding.
print("2 ** 100 =", Integer(2) ** 100)
print("1/3 + 1/6 =", Rational(1, 3) + Rational(1, 6))
# A Float has the precision you choose and rounds each result once.
var precise = ArithmeticContext(format=FloatFormat(256))
var root = sqrt(Float(2, context=precise))
print("sqrt(2) =", root.to_string(10, digits=60))
# Decimal text is exact; a native Float64 brings its binary rounding error along.
print("0.1 from text =", Float("0.1", context=precise).to_string(10, digits=60))
print("0.1 as Float64 =", Float(Float64(0.1), context=precise).to_string(10, digits=60))
# Complex numbers round each component once.
var z = Complex("1+2j") * Complex("3-4j")
print("(1+2j)(3-4j) =", String(z.real().to_integer_exact()) + "+" + String(z.imag().to_integer_exact()) + "j")
var w = sqrt(Complex(-4))
print("sqrt(-4) =", String(w.real().to_integer_exact()) + "+" + String(w.imag().to_integer_exact()) + "j")
Run from the repository root pixi run mojo run -I src docs/examples/quickstart.mojo
Output
2 ** 100 = 1267650600228229401496703205376
1/3 + 1/6 = 1/2
sqrt(2) = 1.41421356237309504880168872420969807856967187537694807317668
0.1 from text = 0.100000000000000000000000000000000000000000000000000000000000
0.1 as Float64 = 0.100000000000000005551115123125782702118158340454101562500000
(1+2j)(3-4j) = 11+2j
sqrt(-4) = 0+2j
The integers and fractions in this example stay exact as their storage grows.
The Float calculation uses 256 bits of precision, so sqrt(2) returns the
correctly rounded 256-bit approximation. Each operation has this rounding
guarantee; a sequence of operations can still accumulate error.
Supply decimal values as text to avoid rounding them through a native float
first. Rational("0.1") is exactly one tenth; Float("0.1") rounds one tenth
to a binary approximation. Importing Float64(0.1) starts with the
approximation already stored in the native float.
Number families¶
| Family | What it holds | Rounding |
|---|---|---|
Integer |
Signed whole numbers | Exact, within storage limits |
Rational |
Fractions in lowest terms | Exact, within storage limits |
ExactComplex |
A real and an imaginary Rational |
Exact, within storage limits |
Float |
Binary floating-point values with a chosen precision and exponent range | Once per operation |
Complex |
A real and an imaginary Float |
Once per component |
Ball |
A real interval described by a midpoint and radius | The bounds include input uncertainty and rounding error |
ComplexBall |
A rectangle described by two Ball components |
Each component encloses all possible results |
Integer and rational arithmetic stays exact: Integer + Rational returns a
Rational. Mixing with a Float produces a rounded result. Named functions
such as add and sqrt accept a context when the selected number family
needs one. See the family references for accepted operands and return types.
Batches, mapping and reductions¶
Batch[T] holds Integer, Rational, Float, Complex, Ball, or ComplexBall values.
Copies and slices keep their values when the original changes. Integer,
rational, float, and complex batches provide arithmetic operators,
comparisons, reductions, and JSON.
Ball batches support NumPy-style functions such as batch.add and batch.exp,
and mapping with vmap or lift. Real Ball batches support min and max;
both ball families support cumsum and cumprod, but have no arithmetic
operators or batch JSON. ExactComplex is a scalar type, with no batch support.
This example applies a scalar function to a batch, then reduces its results:
"""A first batch calculation with an explicit precision and a reduction."""
from apn_mojo import ArithmeticContext, Batch, FloatFormat, Integer, batch
def main() raises:
var values = Batch[Integer]([1, 4, 9, 16])
var context = ArithmeticContext(format=FloatFormat(256))
var roots = batch.sqrt(values, context=context)
print("roots:", roots)
print("sum:", batch.sum(roots, context=context))
Run from the repository root pixi run mojo run -I src docs/examples/batch_quickstart.mojo
Output
roots: [1.0, 2.0, 3.0, 4.0]
sum: 10.0
With vmap[f], you can apply library functions or your own scalar functions
across batch arguments. lift[f] builds on binary functions for integers,
rationals, floats, complex numbers, and balls. Large supported operations can
use a worker pool; short runs and some mapping signatures run on the caller
thread. The batch reference describes those rules.
For floating-point and complex totals, sum and dot keep intermediates
exact and round only the final result. The
reduction reference also covers explicit rounding
orders, axes, contexts, and custom folds.
Find your starting point¶
| I want to... | Read |
|---|---|
| Do exact integer arithmetic | Integer values |
| Keep fractions exact | Exact fractions |
| Compute with a chosen precision | Floats of any precision |
| Work with exact or rounded complex numbers | Complex numbers |
| Carry guaranteed interval bounds | Calculations with bounds |
| Choose precision and check reliable digits | Precision and accuracy |
| Work on many values at once | Batches and selections |
| Map a custom function or build a fold | Vectorization with vmap and lift |
| Choose a thread count | Thread controls |
| Export native arrays | Native values |
| Handle bad input or a failed update | Errors and recovery |
| Save values without losing digits | Text and JSON |
| Look up a function or type | API reference |
| Browse runnable examples | Examples |
| Understand the implementation | Architecture |
See support and limitations for supported platforms and library boundaries.