XRationals.jl

Exact rational arithmetic with IEEE-like special values (NaN, Inf, -Inf), overflow-safe saturation, lazy normalization, and zero heap allocation.

Overview

XRationals.jl is built for exact rational arithmetic when you want fixed-width performance characteristics instead of arbitrary-precision growth. The package keeps numerators and denominators in compact integer fields, avoids heap allocation in normal arithmetic, and gives the exported Qx types a predictable saturation model.

The exported Qx types are the practical front door for the package: they preserve exact rational semantics while adding IEEE-like Inf, -Inf, and NaN values plus lazy normalization for faster chained operations. The public manual focuses on these five exported widths.

XRationals.jl provides five exported rational number types:

TypeIntOverflowNormalization
XRational32 (Qx32)Int32SaturatesLazy via Int64
XRational64 (Qx64)Int64SaturatesLazy via Int128
XRational128 (Qx128)Int128SaturatesLazy via Int256
XRational256 (Qx256)Int256SaturatesLazy via Int512
XRational512 (Qx512)Int512SaturatesLazy via Int1024

Choosing a type

  • Need IEEE-like robustness and speed? Use Qx32, Qx64, Qx128, Qx256, or Qx512. Overflow saturates to Inf/NaN, and lazy normalization gives strong speedups over Rational{Int} for chained arithmetic.
  • Do not need Inf/NaN semantics? Use Rational{Int32}, Rational{Int64}, Rational{Int128}, Rational{Int256}, or Rational{Int512} instead.

Quick start

using XRationals

# Basic exact arithmetic
a = Qx32(2, 3)
b = Qx32(5, 7)
a + b   # 29//21
a * b   # 10//21
a ^ 3   # 8//27

# IEEE-like special values
Qx32(1, 0)   # Inf
Qx32(-1, 0)  # -Inf
Qx32(0, 0)   # NaN

# Overflow saturates instead of crashing
Qx32(typemax(Int32), 1) + 1   # Inf

# Lazy normalization: GCD deferred until display
Qx64(6, 8) == Qx64(3, 4)   # true (cross-multiply comparison)

Key features

  • Zero heap allocation: all arithmetic uses fixed-width integers (Int32/Int64/Int128/Int256/Int512/Int1024/Int2048)
  • Broader width coverage: choose 32-, 64-, 128-, 256-, or 512-bit exported extended rationals from the same API shape
  • Lazy normalization: GCD is deferred until display, hashing, or conversion
  • typemin rejection: constructors reject typemin(Int32), typemin(Int64), typemin(Int128), typemin(Int256), and typemin(Int512) to prevent silent negation overflow
  • Fused multiply-add: fma(x, y, z) computes x*y + z with exact intermediate precision
  • Cross-width conversion: exact widening constructors (Qx64(x::Qx32), Qx128(x::Qx32), Qx128(x::Qx64), Qx256(x::Qx32), Qx256(x::Qx64), Qx256(x::Qx128), Qx512(x::Qx32), Qx512(x::Qx64), Qx512(x::Qx128), Qx512(x::Qx256)) are the canonical widening APIs and preserve value, widen(Qx32) == Qx64, widen(Qx64) == Qx128, widen(Qx128) == Qx256, and widen(Qx256) == Qx512 expose the same widening ladder, and Qx256(x::Qx512), Qx128(x::Qx512), Qx64(x::Qx512), Qx32(x::Qx512), Qx128(x::Qx256), Qx64(x::Qx256), Qx32(x::Qx256), Qx64(x::Qx128), Qx32(x::Qx128), and Qx32(x::Qx64) compute the nearest representable narrower value
  • IEEE ordering: NaN sorts last, Inf/-Inf compare correctly

Benchmarks

The repo includes a runnable benchmark harness at test/Benchmark.jl:

julia --project=. test/Benchmark.jl

Representative local results from that harness:

Qx32 vs Rational{Int32}

OperationRational{Int32}Qx32Speedup
a + b13 ns2 ns7.0x
a * b8 ns2 ns4.2x
a / b7 ns2 ns3.6x
muladd(a,b,a)25 ns3 ns7.5x
a+b+c+d66 ns5 ns14.3x
a*b-c*d40 ns4 ns10.1x

Qx64 vs Rational{Int64}

OperationRational{Int64}Qx64Speedup
a + b14 ns3 ns5.3x
a * b9 ns2 ns4.0x
a / b8 ns3 ns3.2x
muladd(a,b,a)28 ns6 ns4.6x
a+b+c+d72 ns8 ns9.4x
a*b-c*d43 ns5 ns8.2x

Qx128 vs Rational{Int128}

OperationRational{Int128}Qx128Speedup
a + b75 ns7 ns10.4x
a * b65 ns6 ns10.8x
a / b60 ns7 ns8.9x
muladd(a,b,a)144 ns12 ns11.7x
a+b+c+d269 ns20 ns13.2x
a*b-c*d216 ns17 ns12.5x

Qx256 vs Rational{Int256}

OperationRational{Int256}Qx256Speedup
a + b267 ns23 ns11.4x
a * b230 ns16 ns14.3x
a / b208 ns19 ns11.1x
muladd(a,b,a)438 ns38 ns11.4x
a+b+c+d996 ns69 ns14.5x
a*b-c*d791 ns53 ns14.8x

Qx512 vs Rational{Int512}

OperationRational{Int512}Qx512Speedup
a + b583 ns93 ns6.3x
a * b461 ns59 ns7.9x
a / b417 ns64 ns6.5x
muladd(a,b,a)941 ns156 ns6.0x
a+b+c+d2.1 us310 ns6.8x
a*b-c*d1.7 us222 ns7.9x

fma is slower than stdlib Rational because XRationals routes finite fused multiply-add through a slower exact-or-canonical path before converting back to the fixed-width result. Use muladd when that extra guarantee is not needed.

Pages