Julia bindings for the Stillwater Universal
C++ number-systems library. Exposes posits, classic floats (cfloat),
logarithmic number systems (LNS), takums, IBM hex float (hfloat), and
decimal float (dfloat) as first-class Julia numbers that implement AbstractFloat
and compose with the standard library.
pkg> add UniversalNumbersThe pre-built bridge library is downloaded automatically via UniversalNumbers_jll; no C++ compiler or CMake is needed.
UniversalNumbers.jl requires Julia ≥ 1.10 (install Julia with juliaup or
a platform installer from the downloads page).
Linux or macOS: The following will install the latest stable version of Julia, as well as the juliaup tool. Start Julia from the command-line by typing julia. See juliaup --help for how to configure installed versions. If you prefer to use manual installation using a GUI-based installer, see the Manual Downloads page.
curl -fsSL https://install.julialang.org | shWindows: Install Julia using the MSIX App Installer. Alternatively, if you have access to the Microsoft Store, you can install Julia by running the following in the command prompt.
winget install --name Julia --id 9NJNWW8PVKMN -e -s msstoreTwo images are provided; neither needs a local Julia install.
User image (Dockerfile.user) installs the prebuilt package
from the General registry (the C++ bridge arrives as the UniversalNumbers_jll
binary, so no C++ toolchain needed) and preloads the packages used by the bundled examples. Use this to run and experiment:
docker build -f Dockerfile.user -t universalnumbers:user .
docker run --rm -it universalnumbers:user # Julia REPL, package ready
docker run --rm -it universalnumbers:user julia examples/quire.jl # run a bundled exampleRun examples as julia examples/<name>.jl in this image (the default project is preset); do not pass --project=examples, which expects a from-source build.
Source image (Dockerfile) compiles the C++ bridge from source
(cmake and g++ live in the image) and precompiles the package. Use this to build and
test from source or to reproduce CI:
docker build -t universalnumbers .
docker run --rm -it universalnumbers # Julia REPL with the package
docker run --rm -it universalnumbers julia --project=. test/runtests.jl # run the test suiteusing UniversalNumbers
a = Posit{16,1}(1.5)
b = Posit{16,1}(2.5)
a + b # Posit{16,1}(4.0)
sqrt(Posit{32,2}(2.0)) # Posit{32,2}(1.4142135623842478)
a + 2.5 # Promotes 2.5 -> Posit{16,1}: Posit{16,1}(4.0)
using LinearAlgebra
A = [Posit{32,2}(4) Posit{32,2}(1); Posit{32,2}(1) Posit{32,2}(3)]
b = [Posit{32,2}(1), Posit{32,2}(2)]
A \ b # Solve Ax = b in posit arithmeticEach type is a Julia AbstractFloat subtype backed by a specific Universal C++ template instantiation. The type parameters are always integers (no trailing block-type argument needed from user code).
| Julia type | Universal C++ type | Bits | Notes |
|---|---|---|---|
Posit{8,0} |
posit<8,0> |
8 | LUT-accelerated |
Posit{8,1} |
posit<8,1> |
8 | LUT-accelerated |
Posit{8,2} |
posit<8,2> |
8 | LUT-accelerated |
Posit{12,1} |
posit<12,1> |
12 (16-bit word) | |
Posit{16,1} |
posit<16,1> |
16 | |
Posit{16,2} |
posit<16,2> |
16 | |
Posit{19,2} |
posit<19,2> |
19 (32-bit word) | |
Posit{19,3} |
posit<19,3> |
19 (32-bit word) | |
Posit{32,2} |
posit<32,2> |
32 | |
Posit{64,2} |
posit<64,2> |
64 | |
Posit{64,3} |
posit<64,3> |
64 | |
CFloat{8,2} |
cfloat<8,2> |
8 | LUT-accelerated |
CFloat{8,3} |
cfloat<8,3> |
8 | LUT-accelerated; alias E3M4 |
CFloat{8,4} |
cfloat<8,4> |
8 | LUT-accelerated; alias E4M3 |
CFloat{8,5} |
cfloat<8,5> |
8 | LUT-accelerated; alias E5M2 |
CFloat{24,5} |
cfloat<24,5> |
24 (32-bit word) | |
LNS{16,5} |
lns<16,5> |
16 | multiply/divide exact in log domain |
LNS{32,16} |
lns<32,16> |
32 | multiply/divide exact in log domain |
Takum{8} |
takum<8,3> |
8 | LUT-accelerated; rbits=3 per standard |
Takum{16} |
takum<16,3> |
16 | rbits=3 per standard |
Takum{32} |
takum<32,3> |
32 | rbits=3 per standard |
Takum{64} |
takum<64,3> |
64 | rbits=3 per standard |
Fixed{8,4} |
fixpnt<8,4> |
8 | LUT-accelerated; 4 fractional bits; modular |
Fixed{16,8} |
fixpnt<16,8> |
16 | 8 fractional bits; modular |
Fixed{32,16} |
fixpnt<32,16> |
32 | 16 fractional bits; modular |
HFloat{6,7} |
hfloat<6,7> |
32 | IBM hfp32; no NaN/Inf |
HFloat{14,7} |
hfloat<14,7> |
64 | IBM hfp64; no NaN/Inf |
DFloat{7,6} |
dfloat<7,6,BID> |
32 | IEEE 754-2008 decimal32 |
DFloat{16,8} |
dfloat<16,8,BID> |
64 | IEEE 754-2008 decimal64 |
BF16 |
bfloat16 |
16 | Google Brain float; same exponent range as Float32 |
DD |
dd |
128 | double-double; ~31 decimal digits (~106 bits) |
Using an unregistered combination raises an informative error:
julia> Posit{24,1}(1.0)
ERROR: Posit{24, 1} is not instantiated in the registry. Please add it to
UniversalNumbers.TYPE_REGISTRY and rebuild.To request a new type, open an issue or see CONTRIBUTING.md.
All types implement the full AbstractFloat interface:
- Construction from any
Real:Posit{16,1}(1.5),CFloat{8,2}(2),LNS{16,5}(pi) - Conversion back:
Float64(x),Float32(x) - Arithmetic:
+,-,*,/, unary-,abs - Math functions:
sqrt,sin,cos,exp,log-- computed in the native number system by Universal's C++ math library; rounding is determined by the type's parameters - Comparisons:
==,<,<=use Universal's native operators -- exact, no float round-trip - Promotion: mixed expressions like
p + 2.5or2 * pconvert the standard number into the universal type; the computation happens in posit/cfloat/LNS/takum arithmetic - Constants:
zero,one,eps,floatmin,floatmax - Predicates:
iszero,isnan,isinf - Adjacent values:
nextfloat(x)/prevfloat(x)via Universal's++/--operators - Random generation:
rand(Posit{16,1}),rand(Posit{16,1}, 4, 4) - Broadcasting: full
.syntax --sin.(A),A .+ 1.0
The raw bit encoding is always accessible as x.data.
Posits have no Inf and no signed zero; instead they reserve one encoding 100...0 called NaR (Not-a-Real). Its semantics differ from IEEE NaN:
julia> n = Posit{16,1}(NaN) # NaN converts to NaR
Posit{16,1}(NaN)
julia> isnan(n)
true
julia> n == n # NaR == NaR is TRUE (IEEE NaN: false)
true
julia> n < Posit{16,1}(-1e6) # NaR sorts below every real
true
julia> isnan(n + Posit{16,1}(1.0)) # NaR is absorbing
true
julia> isnan(sqrt(Posit{16,1}(-1.0))) # invalid ops produce NaR
true-NaR is NaR (its own 2's complement); Posit{16,1}(NaN).data == 0x8000.
All properties follow from the posit standard (useed = 2^(2^ES)); all registered types are verified by the test suite.
- Reciprocal symmetry:
floatmin(T) * floatmax(T) == 1.0exactly -- every dynamic-range extreme has an exact reciprocal. IEEEFloat16givesfloatmin * floatmax ≈ 4. - Sign symmetry: negation is 2's complement of the encoding; every value has an exact negation.
- One zero, one NaR: no
-0, noInf. - Tapered precision: accuracy peaks near ±1 and tapers at the extremes.
| Type | useed | maxpos | minpos | minpos·maxpos |
|---|---|---|---|---|
Posit{8,0} |
2 | 2^6 = 64 | 2^-6 ≈ 1.56e-2 | 1.0 |
Posit{8,1} |
4 | 4^6 = 4096 | 4^-6 ≈ 2.44e-4 | 1.0 |
Posit{8,2} |
16 | 16^6 ≈ 1.68e7 | 16^-6 ≈ 5.96e-8 | 1.0 |
Posit{12,1} |
4 | 4^10 ≈ 1.05e6 | 4^-10 ≈ 9.54e-7 | 1.0 |
Posit{16,1} |
4 | 4^14 ≈ 2.68e8 | 4^-14 ≈ 3.73e-9 | 1.0 |
Posit{16,2} |
16 | 16^14 ≈ 7.21e16 | 16^-14 ≈ 1.39e-17 | 1.0 |
Posit{19,2} |
16 | 16^17 ≈ 2.95e20 | 16^-17 ≈ 3.39e-21 | 1.0 |
Posit{19,3} |
256 | 256^17 ≈ 8.71e40 | 256^-17 ≈ 1.15e-41 | 1.0 |
Posit{32,2} |
16 | 16^30 ≈ 1.33e36 | 16^-30 ≈ 7.52e-37 | 1.0 |
Posit{64,2} |
16 | 16^62 ≈ 4.52e74 | 16^-62 ≈ 2.21e-75 | 1.0 |
Posit{64,3} |
256 | 256^62 ≈ 2.05e149 | 256^-62 ≈ 4.90e-150 | 1.0 |
A few types behave in ways worth knowing before you rely on them:
HFloatandDFloatparameters count digits, not total bits.HFloat{N,ES}hasN= hex-fraction digits andES= exponent bits, soHFloat{6,7}is a 32-bit type (1 + 7 + 6×4 = 32). LikewiseDFloat{N,ES}usesN= significand digits. This differs fromPosit/CFloat/LNS, where the first parameter is the total bit width.HFloat,DFloat, andFixedhave no NaN or Inf. None reserve encodings for them, soisnan(x)andisinf(x)always returnfalse, and there is no overflow sentinel.Fixedis modular. Arithmetic that exceeds the range wraps around (2's-complement modular) rather than saturating or producing Inf.sqrtof a negative andlogof a non-positiveFixedvalue return0rather than raising an error.TakumNaR is unordered. Unlike posit NaR (which sorts below every real),NaR < xisfalsefor allxunder the takum standard, so comparisons against takum NaR follow IEEE-NaN-like ordering. See the NaR section above for posit behavior.DD(double-double) steps are tiny.nextfloat(DD(1.0))increments by ~2⁻¹⁰⁶, which is belowFloat64print precision, so it displays asDD(1.0)even though the stored value did change.
All types compose with Julia's LinearAlgebra (computations run in the chosen number system):
using UniversalNumbers, LinearAlgebra
A = [Posit{16,1}(1.0) Posit{16,1}(2.0);
Posit{16,1}(3.0) Posit{16,1}(4.0)]
v = [Posit{16,1}(1.0), Posit{16,1}(1.0)]
A * v # [Posit{16,1}(3.0), Posit{16,1}(7.0)]
dot(v, v) # Posit{16,1}(2.0)
lu(A) # LU decomposition in posit arithmetic
det(A) # Posit{16,1}(-2.0)This makes it straightforward to study how alternative number systems behave in real numerical kernels -- e.g. compare a Posit{16,1} matrix factorization against Float16/Float32 baselines.
Note on LNS: logarithmic numbers quantize in the log domain, so products and quotients are exact while values like 1.5 are stored as the nearest representable power-of-two
fraction. This is inherent to the number system.
Posits carry an associated quire, a wide fixed-point accumulator that sums products with no intermediate rounding. fdp uses it to compute an exact fused dot product;
every term is accumulated exactly and the result is rounded only once, at the end:
a = rand(Posit{32,2}, 1000)
b = rand(Posit{32,2}, 1000)
sum(a .* b) # ordinary dot product: rounds after every * and +
fdp(a, b) # quire: exact accumulation, one final roundingFor hand-rolled accumulation, build a Quire directly:
q = Quire(Posit{32,2})
for i in eachindex(a, b)
fma_product!(q, a[i], b[i]) # q += a[i]*b[i], exactly
end
Posit{32,2}(q) # round onceThe quire is opt-in and posit-only -- ordinary posit arithmetic and dot products are
unchanged, so rounded and fused results can be compared in the same program. See
examples/quire.jl for a worked accuracy comparison.
printbits(x) prints the raw encoding with ANSI colors identifying each field:
julia> printbits(Posit{16,1}(1.5))
Posit{16,1}(1.5) 0|10|0|1000000000000
S sign R regime E exponent f fraction
julia> printbits(Takum{16}(1.5))
Takum{16}(1.5) 0|1|000| |10000000000
S sign D direction R regime C characteristic M mantissaField coloring by family:
| Color | Posit | CFloat | LNS | Takum | Fixed | HFloat | DFloat |
|---|---|---|---|---|---|---|---|
| Red | sign | sign | sign | sign | — | sign | sign |
| Yellow | regime | — | — | regime | — | — | combination |
| Cyan | exponent | exponent | integer | characteristic | integer (incl. sign) | exponent | combination |
| Magenta | fraction | fraction | fraction | mantissa | fraction | hex-fraction | significand |
| Green | — | — | — | direction (D) | — | — | — |
UniversalNumbers.jl/
├── src/
│ ├── UniversalNumbers.jl Julia module (parametric types, ccall dispatch)
│ ├── libuniversal_wrapper.cpp C ABI bridge (compiled into UniversalNumbers_jll)
│ ├── quire.jl Exact fused dot product (quire) for posits
│ ├── lut8.jl Precomputed 8-bit lookup tables
│ ├── about.jl Pure-Julia bit-field decoder (printbits / about)
│ ├── LU.jl Unpivoted LU factorization and solve
│ └── QR.jl Givens QR factorization and solve
├── test/
│ ├── runtests.jl Test entry point (full suite)
│ ├── posits.jl Posit arithmetic, math, edge cases
│ ├── takums.jl Takum arithmetic tests
│ ├── lns.jl LNS arithmetic tests
│ ├── la.jl Cross-family linear algebra tests
│ ├── linalg_lu.jl LU decomposition / solve tests
│ ├── linalg_qr.jl QR decomposition / solve tests
│ ├── math_linalg.jl Parametric-interface tests
│ ├── printbits.jl Bit-inspection tests
│ └── broadcasting.jl Broadcasting and array tests
├── examples/
│ ├── quire.jl Quire vs naive dot product accuracy comparison
│ ├── chebyshev.jl Chebyshev nodes and approximation
│ ├── lorenz.jl Lorenz attractor visualization
│ └── ... (18 example scripts in all)
├── deps/universal/ Vendored Stillwater Universal C++ headers
├── build_tarballs.jl BinaryBuilder recipe for UniversalNumbers_jll
├── CMakeLists.txt Build definition for the C++ bridge
├── Dockerfile Build-from-source container image
├── Project.toml Julia package manifest
├── CONTRIBUTING.md Adding types, building from source, JLL workflow
├── LICENSE
└── README.md (this file)
Contributions are welcome, including bug reports and build/CI improvements. See CONTRIBUTING.md for the full guide (adding a type, the JLL build, and design notes).
Building from source is only needed for development -- users get the pre-built bridge library automatically via UniversalNumbers_jll. Prerequisites:
- Julia ≥ 1.10
- CMake ≥ 3.20
- A C++23 compiler (GCC 12+, Clang 15+, MSVC 2022+)
git clone https://github.com/jamesquinlan/UniversalNumbers.jl
cd UniversalNumbers.jl
cmake -S . -B build && cmake --build build # builds build/libuniversal.so
julia --project=. -e 'using Pkg; Pkg.instantiate()' # resolve/download deps
julia --project=. test/runtests.jl # run the test suiteWhen running from source the module loads build/libuniversal.so directly; the installed package uses the JLL artifact instead. Registration with the Julia General registry and
Yggdrasil is handled by the maintainer -- please open build-related pull requests against this repository rather than submitting to
Yggdrasil directly.
% --- Cite this package ---
@software{quinlan2026universalnumbers,
author = {Quinlan, James and Arciero, Michael},
title = {UniversalNumbers.jl: Next-generation computer arithmetic in Julia},
month = jul,
year = 2026,
publisher = {Zenodo},
version = {0.1.3},
doi = {10.5281/zenodo.21629613},
url = {https://doi.org/10.5281/zenodo.21629613},
}
% --- Please also cite this package ---
@article{omtzigt2023universal,
title = {Universal Numbers Library: Multi-format Variable Precision Arithmetic Library},
author = {Omtzigt, E Theodore L and Quinlan, James},
journal = {Journal of Open Source Software},
volume = {8},
number = {83},
pages = {5072},
year = {2023}
}
@article{gustafson2017beating,
title = {Beating Floating Point at its Own Game: Posit Arithmetic},
author = {Gustafson, John L. and Yonemoto, Isaac},
journal = {Supercomputing Frontiers and Innovations},
volume = {4},
number = {2},
pages = {71--86},
year = {2017}
}
@article{hunhold2024takum,
title = {Beating Posits at Their Own Game: Takum Arithmetic},
author = {Hunhold, Laslo},
journal = {Next Generation Arithmetic (CoNGA)},
year = {2024},
note = {Lecture Notes in Computer Science, Springer}
}
@techreport{ieee754_2019,
author = {{IEEE}},
title = {{IEEE} Standard for Floating-Point Arithmetic},
institution = {Institute of Electrical and Electronics Engineers},
year = {2019},
number = {IEEE Std 754-2019},
month = jul,
doi = {10.1109/IEEESTD.2019.8766229},
pages = {1--84}
}
@article{julia_lang,
author = {Jeff Bezanson and Alan Edelman and Stefan Karpinski and Viral B. Shah},
title = {Julia: A Fresh Approach to Numerical Computing},
journal = {SIAM Review},
year = {2017}
}
@techreport{positstandard2022,
title = {Standard for Posit\textsuperscript{TM} Arithmetic (2022)},
author = {{Posit Working Group}},
institution = {National Supercomputing Centre (NSCC) Singapore},
year = {2022},
month = {March}
}
@article{wang2019bfloat16,
title = {BFloat16: The secret to high performance on Cloud TPUs},
author = {Wang, Shibo and Kanwar, Pankaj},
journal = {Google Cloud Blog},
volume = {4},
number = {1},
year = {2019}
}
If you use UniversalNumbers.jl in your work, please cite the archived software. GitHub's
Cite this repository button reads CITATION.cff and produces a ready
citation; the BibTeX entry above is also available.
The concept DOI 10.5281/zenodo.21456649 always resolves to the latest release. To cite a specific version instead, use its version DOI: (v0.1.1 is 10.5281/zenodo.21456650); (v0.1.2 is 10.5281/zenodo.21462151); (v0.1.3 is 10.5281/zenodo.21629613); each release gets its own on Zenodo.
MIT -- see LICENSE.
This package vendors the header-only Stillwater Universal
library under deps/universal/, which provides the underlying
number-system implementations. Universal is distributed under the MIT License,
© 2017 Stillwater Supercomputing, Inc.; its license is retained at
deps/universal/LICENSE and provenance (the exact
vendored upstream commit) is recorded in
deps/universal/VENDORED.md.