Limitless
This might read like lab notes written after too much espresso, but the math is still correct.
limitless numbers starts from a simple premise: useful numbers do not always fit in 32 or 64 bits, and rounding is not “good enough” when exactness matters.
Most numeric bugs are representation bugs, not theory bugs wearing fake mustaches and passing CI exactly once.
intoverflows silently or wrapsdoublecannot represent most decimal fractions exactly- conversions between types lose information
- two values that look equal in source are not equal in memory
If your problem domain is finance, symbolic transforms, exact ratios, cryptography exercises, protocol math, or generated test vectors, these issues show up fast, usually during demos.
So limitless had one job: exact arithmetic until memory runs out, with a C API that is direct, portable, and small enough to embed without summoning dependency dragons.
One number type, two exact forms
At runtime the type is a tagged union with two modes and zero drama:
- integer: arbitrary-precision signed bigint
- rational:
num/den, both bigints, always normalized
The mental model has three rules and no hidden DLC:
- If a result is an integer, keep it integer.
- If an operation requires a fraction, promote to rational.
- If a rational simplifies back to denominator
1, collapse to integer again.
So 6 / 3 stays integer 2, but 7 / 3 becomes exact 7/3. No hidden float conversion, no binary rounding drift, no emergency “why is 0.3 weird” meeting.
This is not “big float.” It is exact integer/rational arithmetic.
The real constraint is invariants, not operators
Adding + - * / is straightforward.
Keeping invariants intact after every operation is the real fight:
- denominator is always positive
- zero has one canonical representation
- rational is always reduced (
gcd(num, den) = 1) - outputs are failure-atomic (on error, caller output is unchanged)
If those invariants drift, everything above them starts wobbling like a shopping cart with one broken wheel and a race condition.
So internally the library normalizes aggressively and uses temporary compute-and-swap patterns so error handling is predictable.
Many C libraries stash allocator state globally. Convenient for demos, painful in real use.
limitless uses a per-context model:
- allocator hooks live in
limitless_ctx - no hidden global mutable state
- no mandatory libc dependency if you override alloc/realloc/free
- thread safety is naturally scoped by context ownership
There is also a beginner path:
limitless_ctx_init_default(&ctx)for quick startlimitless_ctx_init(&ctx, &alloc)for custom environments
So you can start with defaults, then switch to arena/pool/failing allocators without rewriting API usage or sacrificing sanity.
Big integer magnitude is a little-endian limb array. Yes, “limbs.” Welcome to bigint country.
- limbs are 32-bit by default
- optional 64-bit limb mode is available when supported
- sign is tracked separately from magnitude
- zero is represented canonically (
sign=0,used=0)
The little-endian limb layout keeps carry/borrow loops simple and cache-friendly for schoolbook ops.
Rationals are just two bigints with strict normalization rules. Sounds trivial, but keeping that stable across parse/format/ops/conversions is where the complexity piles up.
Division is the key semantic choice and where many libraries quietly betray you.
In many libraries, integer division truncates unless you call a separate rational function. In limitless, limitless_number_div() returns exact math by default, with no truncation jump-scare:
- integer result if divisible
- rational otherwise
- divide-by-zero is explicit
LIMITLESS_EDIVZERO
That makes expressions safer because the default is mathematically correct, not “we rounded, crossed fingers, shipped.”
Other operations are standard exact arithmetic with type promotion where needed:
- add/sub/mul across int/rat combinations
- unary neg/abs
- total compare via exact cross-multiply logic
Advanced integer operations
A second layer exposes integer-only helpers for deliberate number wizardry:
gcdpow_u64(exponentiation by squaring)modexp_u64(modular exponentiation)
These are useful for crypto toy projects, number theory utilities, and parser/generator test fixtures where correctness has to survive contact with reality.
The rules stay strict:
- integer-only APIs reject non-integer values with
LIMITLESS_ETYPE - modulus constraints are validated
- range and invalid states return status codes, not UB
Most numeric libraries do okay on arithmetic and faceplant on conversion edges.
This one spends real effort there, because conversion bugs often become flaky tests that fail only in CI at 2am:
- parse from bases
2..36 - optional base autodetect (
0x,0b, etc.) - rational text input as
numerator/denominator - formatting with caller buffer and explicit required-length reporting
- integer exports with range checks (
to_i64,to_u64)
There are convenience aliases for beginners too:
from_str()andto_str()as base-10 shortcuts
The formatting API returns LIMITLESS_EBUF when capacity is too small and reports required length so callers can resize safely.
Easy to skip, important in practice, mildly cursed in a very IEEE-754 way.
limitless_number_from_float_exact() and ..._from_double_exact() decode IEEE-754 object representation and construct the exact rational/int value for that bit pattern.
- finite values become exact numbers
NaN/Infare rejected (LIMITLESS_EINVAL)0.1becomes its exact binary-rational equivalent, not a rounded decimal string
That is useful for debugging cross-language serialization and reproducible numeric tests, especially when two runtimes claim agreement while clearly disagreeing.
C and C++ usability without splitting the core
The core stays strict C (limitless.h), with a C++ wrapper (limitless.hpp) layered on top for convenience.
C side:
- explicit lifecycle
- explicit context
- explicit status handling
- no operator overloading fantasy
C++ side:
- RAII wrapper class
- operator overloads for natural expressions
- primitive-left operators (
int + limitless_number, etc.) - parse/string helpers (
parse,str) - works in no-exception builds
So beginners can write:
limitless_number x = 33424234;
limitless_number y = (x + 3) / 2.3f;
C users keep full control over memory and error flow; C++ users get friendlier syntax without changing the math engine under the hood.
Testing had to be more than unit happy paths
For exact arithmetic, confidence comes from combinatorics and failure injection, not a handful of happy-path tests and a prayer.
Test coverage includes:
- generated deterministic vector matrices (large pairwise op sets)
- parse/format round-trips across multiple bases
- float/double exact import edge sets (normals, subnormals, signs)
- property-style randomized checks
- differential checks against Python
fractions.Fraction - allocator fault injection to force OOM at many allocation sites
- failure-atomic guarantees (
outunchanged on failure) - single-TU and multi-TU include model checks
- C++ wrapper equivalence behavior vs C core
So this is not just “worked on my laptop and one lucky CI run.” It is a correctness envelope exercised continuously across many modes.
Packaging and release shape matter too
A lot of open-source libs stop at “here is a header, good luck” and vibes.
This one also ships consumer paths:
- CMake package target:
limitless::limitless - pkg-config metadata
- Conan recipe
- vcpkg overlay port
- GitHub Actions matrix across OS/compiler families
- coverage and release automation
Not glamorous, but this is what makes a library usable by teams and not only by the author on one machine.
What this is good for (and not good for)
Good fit:
- exact fraction arithmetic
- educational math tooling
- parser/transformation pipelines needing deterministic exactness
- interoperability tests where numeric drift is unacceptable
Bad fit:
- performance-critical floating-point simulation
- vectorized numeric workloads
- cases where approximate real arithmetic is both expected and sufficient
It is a precision-first library, not a throughput-first numeric engine, and that tradeoff is intentional.
Check it out or try it out here (open-source): limitless


It lets you sweep the complex plane domain, pick presets (critical strip, first zeros, near the pole), and toggle layers like the critical line, known zeros, reference planes, and divergence region. There are also modes to compare analytic continuation against Dirichlet partial sums or prime zeta partials, plus error and relative error views.