From the README.md:
Exact rational arithmetic with IEEE-like special values (NaN, Inf, -Inf), overflow-safe saturation, and lazy normalization for impressive throughput.
Types
| Type | Alias | Backing | Overflow | Normalization |
|---|---|---|---|---|
XRational32 |
Qx32 |
Int32 |
Saturates to Inf/NaN | Lazy (Int64 intermediate) |
| XRational64 | Qx64 | Int64 | Saturates to Inf/NaN | Lazy (Int128 intermediate) |
Features
- Exact rational arithmetic with no floating-point rounding
- IEEE-like NaN, Inf, -Inf encoded in the same struct (
0//0,1//0,-1//0) - Overflow saturates to Inf/NaN instead of crashing (Qx types)
- Lazy GCD normalization: deferred until display, hashing, or conversion
- 3-13x faster than
Rational{Int}for chained arithmetic - Fused multiply-add (
fma) with exact intermediate computation - Cross-width conversion (Qx64 to Qx32) via best rational approximation
- Zero heap allocation: all arithmetic uses fixed-width integers (Int32/Int64/Int128/Int256)
typeminrejection prevents silent negation overflow
Examples
See Use.jl for examples.
Benchmarks
All operations are zero-allocation unless noted. Times are minimum nanoseconds.
Note: the reason that fma for Qx32 and Qx64 takes longer is that it returns the nearest rational to the true value. Rational{Int32} and Rational{Int64} just perform a muladd. This is the reason for slowdown in that function. You can use muladd with Qx32 and Qx64 for improved performance.
32-bit
| Operation | Rational{Int32} |
Qx32 |
|---|---|---|
| construct(7,3) | 1 ns | 1 ns |
| a + b | 13 ns | 2 ns |
| a - b | 13 ns | 2 ns |
| a * b | 8 ns | 2 ns |
| a / b | 7 ns | 2 ns |
| -a | 1 ns | 1 ns |
| a < b | 1 ns | 2 ns |
| a == b | 1 ns | 1 ns |
| abs(-a) | 1 ns | 2 ns |
| inv(a) | 1 ns | 2 ns |
| a ^ 3 | 18 ns | 5 ns |
| a+b+c+d | 66 ns | 4 ns |
| ab-cd | 37 ns | 4 ns |
| muladd(a,b,a) | 23 ns | 3 ns |
| fma(a,b,a) | 23 ns | 215 ns |
64-bit
| Operation | Rational{Int64} |
Qx64 |
|---|---|---|
| construct(7,3) | 1 ns | 1 ns |
| a + b | 14 ns | 3 ns |
| a - b | 15 ns | 3 ns |
| a * b | 8 ns | 2 ns |
| a / b | 8 ns | 3 ns |
| -a | 1 ns | 1 ns |
| a < b | 1 ns | 2 ns |
| a == b | 1 ns | 1 ns |
| abs(-a) | 1 ns | 2 ns |
| inv(a) | 1 ns | 2 ns |
| a ^ 3 | 21 ns | 7 ns |
| a+b+c+d | 72 ns | 8 ns |
| ab-cd | 41 ns | 5 ns |
| muladd(a,b,a) | 27 ns | 6 ns |
| fma(a,b,a) | 27 ns | 859 ns |
Qx64 vs Rational{Int64} Speedup
Qx64 delays GCD normalization until display, hashing, or conversion, giving IEEE-like Inf/NaN semantics and faster arithmetic than stdlib.
| Operation | Rational{Int64} |
Qx64 |
Speedup |
|---|---|---|---|
| construct(7,3) | 1 ns | 1 ns | ~1x |
| a + b | 14 ns | 3 ns | 5.3x |
| a - b | 15 ns | 3 ns | 5.5x |
| a * b | 8 ns | 2 ns | 3.7x |
| a / b | 8 ns | 3 ns | 3.1x |
| -a | 1 ns | 1 ns | 1.1x |
| a < b | 1 ns | 2 ns | 0.84x |
| a == b | 1 ns | 1 ns | ~1x |
| abs(-a) | 1 ns | 1 ns | 0.89x |
| inv(a) | 2 ns | 2 ns | 0.91x |
| a ^ 3 | 21 ns | 7 ns | 3.1x |
| a+b+c+d | 72 ns | 8 ns | 9.4x |
| ab-cd | 41 ns | 5 ns | 8.3x |
| muladd(a,b,a) | 27 ns | 6 ns | 4.5x |
| fma(a,b,a) | 27 ns | 859 ns | 0.03x |
(benchmarking code is available in the repository)