Zig bindings for the GNU MP (GMP) arbitrary-precision arithmetic library.
By default these bindings link against a system-installed libgmp. GMP is an
autotools-based project with no single amalgamation unit, so the system library
is the fastest path to a CPU-tuned build. Install the development headers:
# Debian / Ubuntu
sudo apt install libgmp-dev
# Fedora / RHEL
sudo dnf install gmp-devel
# macOS (Homebrew)
brew install gmp
The most recent tagged release is built and tested with Zig version 0.16.0
and GMP 6.3.0.
zig fetch --save=gmp \
https://github.com/nDimensional/zig-gmp/archive/refs/tags/v0.1.0+6.3.0.tar.gz
The main branch roughly tracks Zig nightly, which you can install via specific
commit.
zig fetch --save=gmp \
https://github.com/nDimensional/zig-gmp/archive/${COMMIT_HASH}.tar.gz
| Option | Default | Description |
|---|---|---|
-Dsystem_gmp |
true |
Link the system libgmp. Set to false to build GMP from source instead. |
# System libgmp (default)
zig build test
zig build run
# Bundle GMP from source (no system libgmp needed)
zig build -Dsystem_gmp=false test
zig build -Dsystem_gmp=false run
The bundled mode compiles GMP's generic (no-assembly) C source directly via the
Zig build system, fetching the upstream gmp-6.3.0 tarball as a dependency. It
generates the public header (gmp.h) and the seven table files at build time by
compiling and running GMP's own gen-*.c programs with zig cc — no autoconf,
m4, or system C compiler is required.
⚠️ Performance: the bundled build uses GMP's portable generic C limbs, not the per-microarch assembly that a./configure-built systemlibgmpuses. On this host (x86_64, GMP 6.3.0,-OReleaseFast), the measured slowdown versus the assembly-tuned system library was:
workload system libgmpbundled (generic C) slowdown factorial(100000)— 456574 digits~5.9 ms ~15.5 ms ~2.6× 50× full-precision mul, 213237-digit operands ~2.17 ms/op ~3.89 ms/op ~1.8× The gap stays modest at large sizes because the generic build ships the same asymptotic algorithms (Karatsuba, Toom-Cook, FFT) as the assembly build — only the base-case limb routines (
mpn_mul_1,mpn_addmul_1,mpn_sqr_basecase) differ, and those are a small fraction of total time for huge operands. The raw base-case kernels themselves run ~10–100× slower, so workloads dominated by many small/medium-operand multiplications will see a larger gap. Use the bundled mode for self-contained builds where portability matters more than throughput; use the default (-Dsystem_gmp=true) for speed.The committed
src/bundled/config.hassumes a 64-bit little-endian Linux target. For other ABIs, prefer the system library.
Then add gmp as an import to your root modules in build.zig:
fn build(b: *std.Build) void {
const app = b.addExecutable(.{ ... });
// ...
const gmp = b.dependency("gmp", .{});
app.root_module.addImport("gmp", gmp.module("gmp"));
}const std = @import("std");
const gmp = @import("gmp");
pub fn main() !void {
// 100! has 158 digits.
var n = gmp.Int.init();
defer n.deinit();
n.factorial(100);
var buf: [256]u8 = undefined;
std.log.info("100! = {s}", .{n.writeStr(10, &buf)});
// gcd of two large integers.
var a = gmp.Int.init();
defer a.deinit();
var b = gmp.Int.init();
defer b.deinit();
try a.setStr("12345678901234567890", 10);
try b.setStr("98765432109876543210", 10);
var g = gmp.Int.init();
defer g.deinit();
g.gcd(a, b);
std.log.info("gcd = {s}", .{g.writeStr(10, &buf)});
}Int, Rational and Float own heap-allocated GMP state: call init (or
init2) to create one and deinit to free it.
var x = gmp.Int.init();
defer x.deinit();pub const Int = struct {
pub fn init() Int
pub fn init2(bit_count: u64) Int
pub fn deinit(self: *Int) void
pub fn realloc2(self: *Int, bit_count: u64) void
// Assignment
pub fn set(self: *Int, value: Int) void
pub fn setU(self: *Int, value: u64) void
pub fn setI(self: *Int, value: i64) void
pub fn setD(self: *Int, value: f64) void
pub fn setRational(self: *Int, value: Rational) void
pub fn setFloat(self: *Int, value: Float) void
pub fn setStr(self: *Int, str: [*:0]const u8, base: u8) !void
pub fn swap(self: *Int, other: *Int) void
// Conversion
pub fn getU(self: Int) u64
pub fn getI(self: Int) i64
pub fn getD(self: Int) f64
pub fn fitsU(self: Int) bool
pub fn fitsI(self: Int) bool
pub fn sizeInBase(self: Int, base: u8) usize
pub fn writeStr(self: Int, base: u8, buf: []u8) []u8
pub fn toString(self: Int, base: u8, allocator: Allocator) ![]u8
// Arithmetic (self-mutating)
pub fn add(self: *Int, other: Int) void
pub fn sub(self: *Int, other: Int) void
pub fn mul(self: *Int, other: Int) void
pub fn addMul(self: *Int, a: Int, b: Int) void
pub fn subMul(self: *Int, a: Int, b: Int) void
pub fn neg(self: *Int) void
pub fn abs(self: *Int) void
// Division (self-mutating)
pub fn divFloor(self: *Int, other: Int) void
pub fn modFloor(self: *Int, other: Int) void
pub fn divCeil(self: *Int, other: Int) void
pub fn modCeil(self: *Int, other: Int) void
pub fn divTrunc(self: *Int, other: Int) void
pub fn modTrunc(self: *Int, other: Int) void
pub fn divExact(self: *Int, other: Int) void
// Powers / roots
pub fn pow(self: *Int, base: Int, exp: u64) void
pub fn powU(self: *Int, base: u64, exp: u64) void
pub fn powMod(self: *Int, base: Int, exp: Int, mod_: Int) void
pub fn sqrt(self: *Int) void
pub fn root(self: *Int, n: u64) bool // true if exact
// Number theory
pub fn gcd(self: *Int, a: Int, b: Int) void
pub fn gcdExt(self: *Int, x: ?*Int, a: Int, b: Int) void
pub fn lcm(self: *Int, a: Int, b: Int) void
pub fn factorial(self: *Int, n: u64) void
pub fn binomial(self: *Int, n: u64, k: u64) void
pub fn nextPrime(self: *Int) void
pub fn prevPrime(self: *Int) void
pub fn isPrime(self: Int, reps: u32) Primality // .not_prime / .probably_prime / .definitely_prime
// Comparison
pub fn cmp(self: Int, other: Int) std.math.Order
pub fn cmpU(self: Int, value: u64) std.math.Order
pub fn cmpI(self: Int, value: i64) std.math.Order
pub fn sgn(self: Int) i8 // -1, 0, 1
pub fn isZero(self: Int) bool
// Bit operations
pub fn setBit(self: *Int, bit_index: u64) void
pub fn clearBit(self: *Int, bit_index: u64) void
pub fn toggleBit(self: *Int, bit_index: u64) void
pub fn getBit(self: Int, bit_index: u64) u1
pub fn bitAnd(self: *Int, other: Int) void
pub fn bitOr(self: *Int, other: Int) void
pub fn bitXor(self: *Int, other: Int) void
pub fn popcount(self: Int) u64
};pub const Rational = struct {
pub fn init() Rational
pub fn deinit(self: *Rational) void
pub fn set(self: *Rational, value: Rational) void
pub fn setU(self: *Rational, numer: u64, denom: u64) void
pub fn setI(self: *Rational, numer: i64, denom: u64) void
pub fn setInt(self: *Rational, value: Int) void
pub fn setFloat(self: *Rational, value: Float) void
pub fn setStr(self: *Rational, str: [*:0]const u8, base: u8) !void
pub fn setNum(self: *Rational, num: Int) void
pub fn setDen(self: *Rational, den: Int) void
pub fn getNum(self: Rational, out: *Int) void
pub fn getDen(self: Rational, out: *Int) void
pub fn canonicalize(self: *Rational) void
pub fn swap(self: *Rational, other: *Rational) void
pub fn getD(self: Rational) f64
pub fn writeStr(self: Rational, base: u8, buf: []u8) []u8
pub fn toString(self: Rational, base: u8, allocator: Allocator) ![]u8
pub fn add(self: *Rational, other: Rational) void
pub fn sub(self: *Rational, other: Rational) void
pub fn mul(self: *Rational, other: Rational) void
pub fn div(self: *Rational, other: Rational) void
pub fn inv(self: *Rational) void
pub fn neg(self: *Rational) void
pub fn abs(self: *Rational) void
pub fn cmp(self: Rational, other: Rational) std.math.Order
pub fn cmpU(self: Rational, numer: u64, denom: u64) std.math.Order
pub fn cmpI(self: Rational, numer: i64, denom: u64) std.math.Order
pub fn sgn(self: Rational) i8
pub fn isZero(self: Rational) bool
};pub const Float = struct {
pub fn init() Float
pub fn init2(bit_count: u64) Float
pub fn deinit(self: *Float) void
pub fn setPrec(self: *Float, bit_count: u64) void
pub fn getPrec(self: Float) u64
pub fn set(self: *Float, value: Float) void
pub fn setU(self: *Float, value: u64) void
pub fn setI(self: *Float, value: i64) void
pub fn setD(self: *Float, value: f64) void
pub fn setInt(self: *Float, value: Int) void
pub fn setRational(self: *Float, value: Rational) void
pub fn setStr(self: *Float, str: [*:0]const u8, base: u8) !void
pub fn swap(self: *Float, other: *Float) void
pub fn getD(self: Float) f64
pub fn getD2Exp(self: Float) D2Exp // { d: f64, exp: i64 }, self == d * 2^exp
pub fn writeStr(self: Float, base: u8, n_digits: usize, buf: []u8) FloatStr
pub fn toString(self: Float, base: u8, n_digits: usize, allocator: Allocator) ![]u8
pub fn add(self: *Float, other: Float) void
pub fn sub(self: *Float, other: Float) void
pub fn mul(self: *Float, other: Float) void
pub fn div(self: *Float, other: Float) void
pub fn neg(self: *Float) void
pub fn abs(self: *Float) void
pub fn sqrt(self: *Float) void
pub fn floor(self: *Float) void
pub fn ceil(self: *Float) void
pub fn trunc(self: *Float) void
pub fn cmp(self: Float, other: Float) std.math.Order
pub fn cmpD(self: Float, value: f64) std.math.Order
pub fn cmpU(self: Float, value: u64) std.math.Order
pub fn cmpI(self: Float, value: i64) std.math.Order
pub fn eqApprox(self: Float, other: Float, n_bits: u64) bool
pub fn sgn(self: Float) i8
pub fn isZero(self: Float) bool
};Float.toString produces output of the form [-]0.<mantissa>e<exp>, where
value == 0.mantissa * base ^ exp. The sign is determined separately via sgn.
pub const Error = error{
InvalidString, // a string passed to setStr could not be parsed
};GMP aborts the process on allocation failure by default (its standard behavior); these bindings do not install custom memory functions, so out-of-memory is not surfaced as a Zig error.
mpz_t,mpq_tandmpf_tare value types in C containing a pointer to heap-allocated limbs. The Zig wrappers embed the underlying GMP struct by value and exposeinit/deinit; copying anInt/Rational/Float(e.g. passing one by value to a method) is a shallow copy of the handle and is safe for reads, but only one owner should calldeinit.- GMP permits output operands to alias input operands (
c = c + b), so the self-mutating arithmetic methods passselfas both the result and an operand. writeStrwrites into a caller-supplied buffer and returns the written bytes; forInt, size it to at leastsizeInBase(base) + 2.toStringis the allocating convenience equivalent.setStrtakes a null-terminated[*:0]const u8. Usestd.fmt.allocPrintZif you have a[]const u8slice.isPrimereturns.definitely_primeonly for primes GMP can prove deterministically (small ones); large primes are reported as.probably_prime.
MIT © nDimensional Labs