Skip to content

nDimensional/zig-gmp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zig-gmp

Zig bindings for the GNU MP (GMP) arbitrary-precision arithmetic library.

Table of Contents

Installation

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

Build options

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 system libgmp uses. On this host (x86_64, GMP 6.3.0, -OReleaseFast), the measured slowdown versus the assembly-tuned system library was:

workload system libgmp bundled (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.h assumes 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"));
}

Usage

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();

API

Int

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
};

Rational

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
};

Float

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.

Errors

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.

Notes

  • mpz_t, mpq_t and mpf_t are value types in C containing a pointer to heap-allocated limbs. The Zig wrappers embed the underlying GMP struct by value and expose init/deinit; copying an Int/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 call deinit.
  • GMP permits output operands to alias input operands (c = c + b), so the self-mutating arithmetic methods pass self as both the result and an operand.
  • writeStr writes into a caller-supplied buffer and returns the written bytes; for Int, size it to at least sizeInBase(base) + 2. toString is the allocating convenience equivalent.
  • setStr takes a null-terminated [*:0]const u8. Use std.fmt.allocPrintZ if you have a []const u8 slice.
  • isPrime returns .definitely_prime only for primes GMP can prove deterministically (small ones); large primes are reported as .probably_prime.

License

MIT © nDimensional Labs

About

Zig bindings for the GNU Multiple Precision Arithmetic Library

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors