An arbitrary-precision integer, decimal, binary floating-point, and rational library for Mojo, written in pure Mojo, with a 128-bit fixed-point decimal type as well. Inspired by Python's int, Decimal and Fraction, MPFR, and C#/.NET's System.Decimal. Install it with pixi add decimo.
Comes with an interactive arbitrary-precision calculator (REPL + one-shot mode) powered by ArgMojo. Install it with brew install forfudan/tap/decimo.
The same library is packaged for Python as a near drop-in for the standard library's decimal, and a superset of it: the whole of its method surface, plus pi(), e() and a 128-bit decimal type. Install it with pip install decimo.
| Type | Alias | Information | Layout |
|---|---|---|---|
BigInt |
BInt |
Arbitrary-precision binary integer type, equivalent to Python's int and GMP's mpz |
Base-2^64 |
BigDecimal |
BDec, Decimal |
Arbitrary-precision decimal type, equivalent to Python's decimal.Decimal and libmpdec |
Base-10^18 |
BigFloat |
BFlt |
Arbitrary-precision binary float type, equivalent to MPFR's mpfr_t |
Base-2^64 |
Rational |
Frac |
Exact rational number type, equivalent to Python's fractions.Fraction |
Two BigInt |
Decimal128 |
Dec128 |
128-bit fixed-precision decimal type, equivalent to C#/.NET's System.Decimal |
32-bit words |

Base-ten arithmetic, integers with no width limit, and an expression evaluator
Decimo provides arbitrary-precision integer, decimal, binary floating-point and rational types for Mojo. Its decimal types give exact results for financial modeling, scientific computing, and applications where floating-point approximation errors are unacceptable. Beyond basic arithmetic, the library has the exponential, logarithmic, trigonometric and hyperbolic functions.
For Pythonistas, decimo.BigInt to Mojo is like int to Python, decimo.BigDecimal to Mojo is like decimal.Decimal to Python, and decimo.Rational to Mojo is like fractions.Fraction to Python. decimo.BigFloat to Mojo is like MPFR to C, and decimo.Decimal128 to Mojo is like System.Decimal to C# or rust_decimal to Rust.
The core types are1:
- An arbitrary-precision signed integer type
BigInt2 (aliasBInt), which is a Mojo-native equivalent of Python'sint. - An arbitrary-precision decimal implementation (
BigDecimal) (aliasDecimal) allowing for calculations with unlimited digits and decimal places3, which is a Mojo-native equivalent of Python'sdecimal.Decimal. - A 128-bit fixed-point decimal implementation (
Decimal128) (aliasDec128) supporting up to 29 significant digits with a maximum of 28 decimal places4, which is a Mojo-native equivalent of C#'sSystem.Decimalor Rust'srust_decimal. - An arbitrary-precision binary floating-point implementation (
BigFloat) (aliasBFlt), correctly rounded in all seven rounding modes, which is a Mojo-native equivalent of MPFR'smpfr_t5. - An exact rational implementation (
Rational) (aliasFrac), a pair ofBigIntkept in lowest terms, which is a Mojo-native equivalent of Python'sfractions.Fraction.
Decimo is fast: at a million digits pi() is twelve times quicker than pure-Python mpmath, BigInt multiplication is fifteen times quicker than CPython's int, the logarithm at ten thousand digits is four hundred times quicker than libmpdec, and small BigDecimal operations are close to that same C library behind Python's decimal. The measured numbers, with the commit they were taken on, are in docs/benchmarks.md; pixi run benchdoc regenerates them.
Decimo combines "Decimal" and "Mojo" - reflecting its purpose and implementation language. "Decimo" is also a Latin word meaning "tenth" and is the root of the word "decimal".
decimo is a command-line calculator built on the Decimo library and powered by
ArgMojo. Run it with no arguments for an
interactive REPL, or pass an expression / file / piped stdin for one-shot
evaluation. The binary is self-contained β no Mojo or Pixi needed on the user's
machine. See the user manual for the full
reference, and the Quick start below for a taste.

Ask for as many significant digits as you like

Everything decimal has, and things it does not
The Mojo library is also compiled into a Python extension and published on PyPI,
where it stands in for the standard library's decimal. Change one import and
a decimal program keeps working:
# from decimal import Decimal, getcontext
from decimo import Decimal, getcontext
getcontext().prec = 50
print(Decimal(1) / Decimal(7))
# 0.14285714285714285714285714285714285714285714285714The two agree digit for digit. The test suite checks every operation against
the standard library rather than against a table of expected strings, so
"agrees with decimal" is measured rather than claimed.
Nothing is missing: every method decimal.Decimal has, decimo.Decimal has
too. And it goes further. pi() and e() are there, which decimal has
neither of. sqrt, exp, ln and log10 take a rounding= argument, where
decimal ignores the context mode for those and always rounds half to even.
Decimal128 brings trigonometry, cbrt, root and the IEEE 754 interchange
bytes with it. And it is faster once the numbers are large β 2.9x at a
thousand digits β 20-25% behind on small ones, where what is left is the cost
of the Python call rather than the arithmetic.
Where it is not a drop-in it refuses rather than answers differently: no NaN
or infinity, no ROUND_05UP, no signals or traps, and one context per process
rather than per thread. See python/README.md for the
full list with the reasoning, and the
Python quick start below for a taste.
This repository includes a built-in TOML parser
(decimo.toml), a lightweight pure-Mojo implementation supporting TOML v1.0. It
parses configuration files and test data, supporting basic types, arrays, and
nested tables. While created for Decimo's testing framework, it offers
general-purpose structured data parsing with a clean, simple API.
Decimo is available in the modular-community
https://repo.prefix.dev/modular-community package repository. To access this
repository, add it to your channels list in your pixi.toml file:
channels = ["https://conda.modular.com/max", "https://repo.prefix.dev/modular-community", "conda-forge"]Then, you can install Decimo using any of these methods:
-
From the
pixiCLI, run the commandpixi add decimo. This fetches the latest version and makes it immediately available for import. -
In the
mojoproject.tomlfile of your project, add the following dependency:decimo = ">=0.16.0, <0.17.0"
Then run
pixi installto download and install the package. -
For the latest development version in the
mainbranch, clone this GitHub repository and build the package locally using the commandpixi run package.
Package versions and Mojo compatibility
The following table summarizes the package versions and their corresponding Mojo versions:
| library | version | Mojo version | package manager |
|---|---|---|---|
decimojo |
v0.1.0 | ==25.1 | magic |
decimojo |
v0.2.0 | ==25.2 | magic |
decimojo |
v0.3.0 | ==25.2 | magic |
decimojo |
v0.3.1 | >=25.2, <25.4 | pixi |
decimojo |
v0.4.x | ==25.4 | pixi |
decimojo |
v0.5.0 | ==25.5 | pixi |
decimojo |
v0.6.0 | ==0.25.7 | pixi |
decimojo |
v0.7.0 | ==0.26.1 | pixi |
decimo |
v0.8.0 | ==0.26.1 | pixi |
decimo |
v0.9.0 | ==0.26.2 | pixi |
decimo |
v0.10.0 | ==1.0.0b1 | pixi |
decimo |
v0.11.0 | ==1.0.0b2 | pixi |
decimo |
v0.12.0 | >=1.0.0, <1.1.0 | pixi |
decimo |
v0.13.0 | >=1.0.0, <1.1.0 | pixi |
decimo |
v0.14.0 | >=1.0.0, <1.1.0 | pixi |
decimo |
v0.15.0 | >=1.1.0, <1.2.0 | pixi |
decimo |
v0.16.0 | >=1.2.0, <1.3.0 | pixi |
The decimo CLI is distributed via the
forfudan/tap Homebrew tap.
Pre-built binaries are available for macOS arm64 (Apple Silicon) and
Linux x86_64, and ship with the Mojo runtime libraries bundled β you do not
need Mojo or Pixi installed.
brew install forfudan/tap/decimo
decimo --versionOr tap once and use the bare formula name:
brew tap forfudan/tap
brew install decimoTo upgrade to a later release:
brew update && brew upgrade decimopip install decimoWheels are built for macOS arm64 (macOS 11 and later) and for Linux on x86_64 and arm64 (glibc 2.35 and later), for CPython 3.13 and 3.14. Nothing else is needed β the Mojo runtime libraries travel inside the wheel. On any other platform, build from source with pixi:
git clone https://github.com/forfudan/decimo && cd decimo
pixi run -e py314 release # or py313; the wheel lands in python/dist/
pip install python/dist/*.whlYou can start using Decimo by importing the decimo module. An easy way to do
this is to import everything from the prelude module, which provides the most
commonly used types.
from decimo.prelude import *This will import the following types or aliases into your namespace:
BigInt(and its aliasBInt): An arbitrary-precision signed integer type, equivalent to Python'sint.BigDecimal(and its aliasesBDec,Decimal): An arbitrary-precision decimal type, equivalent to Python'sdecimal.Decimal.BigFloat(and its aliasBFlt): An arbitrary-precision binary floating-point type, equivalent to MPFR'smpfr_t.Decimal128(and its aliasDec128): A 128-bit fixed-precision decimal type.RoundingMode: An enumeration for rounding modes.ROUND_DOWN,ROUND_UP,ROUND_CEILING,ROUND_FLOOR,ROUND_HALF_UP,ROUND_HALF_DOWN,ROUND_HALF_EVEN: Constants for all seven rounding modes.
BigDecimal β arbitrary precision, and how to set it
Here are some examples showcasing the arbitrary-precision feature of the
BigDecimal (Decimal) type. For some mathematical operations, the default
precision (number of significant digits) is set to 28. You can change the
precision by passing the precision argument to the function. This default
precision will be configurable globally in future when Mojo supports global
variables.
from decimo.prelude import *
def main() raises:
var a = BigDecimal("123456789.123456789")
var b = Decimal("1234.56789") # Alias of BigDecimal
# === Basic Arithmetic === #
print(a + b) # 123458023.691346789
print(a - b) # 123455554.555566789
print(a * b) # 152415787654.32099750190521
print(a.true_divide(b + 1)) # 99919.06565608207008357913866
# === Exponential Functions === #
print(a.sqrt(precision=80))
# 11111.111066111110969430554981749302328338130654689094538188579359566416821203641
print(a.cbrt(precision=80))
# 497.93385938415242742001134219007635925452951248903093962731782327785111102410518
print(a.root(b, precision=80))
# 1.0152058862996527138602610522640944903320735973237537866713119992581006582644107
print(a.power(exponent=b, precision=80))
# 3.3463611024190802340238135400789468682196324482030786573104956727660098625641520E+9989
print(a.exp(precision=80))
# 1.8612755889649587035842377856492201091251654136588338983610243887893287518637652E+53616602
print(a.log(base=b, precision=80))
# 2.6173300266565482999078843564152939771708486260101032293924082259819624360226238
print(a.ln(precision=80))
# 18.631401767168018032693933348296537542797015174553735308351756611901741276655161
# === Trigonometric Functions === #
print(a.sin(precision=200))
# 0.99985093087193092464780008002600992896256609588456
# 91036188395766389946401881352599352354527727927177
# 79589259132243649550891532070326452232864052771477
# 31418817041042336608522984511928095747763538486886
print(b.cos(precision=1000))
# -0.9969577603867772005841841569997528013669868536239849713029893885930748434064450375775817720425329394
# 9756020177557431933434791661179643984869397089102223199519409695771607230176923201147218218258755323
# 7563476302904118661729889931783126826250691820526961290122532541861737355873869924820906724540889765
# 5940445990824482174517106016800118438405307801022739336016834311018727787337447844118359555063575166
# 5092352912854884589824773945355279792977596081915868398143592738704592059567683083454055626123436523
# 6998108941189617922049864138929932713499431655377552668020889456390832876383147018828166124313166286
# 6004871998201597316078894718748251490628361253685772937806895692619597915005978762245497623003811386
# 0913693867838452088431084666963414694032898497700907783878500297536425463212578556546527017688874265
# 0785862902484462361413598747384083001036443681873292719322642381945064144026145428927304407689433744
# 5821277763016669042385158254006302666602333649775547203560187716156055524418512492782302125286330865
# === Internal representation of the number === #
(
Decimal(
"3.141592653589793238462643383279502884197169399375105820974944"
).power(exponent=2, precision=60)
).print_internal_representation()
# Internal Representation Details of BigDecimal
# ----------------------------------------------
# number: 9.8696044010893586188344909998
# 761511353136994072407906264133
# 5
# coefficient: 986960440108935861883449099987
# 615113531369940724079062641335
# negative: False
# scale: 59
# word 0: 940724079062641335
# word 1: 99987615113531369
# word 2: 440108935861883449
# word 3: 986960
# ----------------------------------------------BigInt β arbitrary-precision signed integers
A quick tour of the main methods of the BigInt (BInt) type.
from decimo.prelude import *
def main() raises:
# === Construction ===
var a = BigInt("12345678901234567890") # From string
var b = BigInt(12345) # From integer
var c = BInt("1991_10,18") # From string with separators and spaces
print(a, b, c)
# === Basic Arithmetic ===
print(a + b) # Addition: 12345678901234580235
print(a - b) # Subtraction: 12345678901234555545
print(a * b) # Multiplication: 152407406035740740602050
# === Division Operations ===
print(a // b) # Floor division: 1000054994024671
print(a.truncate_divide(b)) # Truncate division: 1000054994024671
print(a % b) # Modulo: 4395
# === Power Operation ===
print(BigInt(2).power(10)) # Power: 1024
print(BigInt(2) ** 10) # Power (using ** operator): 1024
# === Comparison ===
print(a > b) # Greater than: True
print(a == BigInt("12345678901234567890")) # Equality: True
print(a.is_zero()) # Check for zero: False
# === Type Conversions ===
print(String(a)) # To string: "12345678901234567890"
# === Sign Handling ===
print(-a) # Negation: -12345678901234567890
print(
abs(BigInt("-12345678901234567890"))
) # Absolute value: 12345678901234567890
print(a.is_negative()) # Check if negative: False
# === Extremely large numbers ===
# 3600 digits // 1800 digits
print(BigInt("123456789" * 400) // BigInt("987654321" * 200))
# === Greatest common divisor ===
print(a.gcd(b)) # Greatest common divisor: 15
print(a.gcd(c)) # Greatest common divisor: 6BigFloat β binary floating point, to as many bits as you ask
A quick tour of the BigFloat (BFlt) type. The precision is a number of
bits and travels with each value, so 53 of them is what a Float64 holds and
200 is what no machine type holds. Every operation below is correctly rounded.
import decimo.bigfloat.constants as bigfloat_constants
import decimo.bigfloat.trigonometric as bigfloat_trigonometric
from decimo.prelude import *
def main() raises:
# === Construction: the precision is in bits, and is per value ===
var a = BigFloat.from_string("1.1", 53) # What a Float64 holds
var b = BFlt.from_string("1.1", 200) # 200 bits of it
print(a.to_string(), b.to_string(20))
# 1.1000000000000001 1.1000000000000000000
# === The four operations, each correctly rounded ===
var third = BigFloat.from_string("1", 200) / BigFloat.from_string("3", 200)
print(third.to_string(20)) # 0.33333333333333333333
print((a + a).to_string(), (b * b).to_string(20))
# 2.2000000000000002 1.2100000000000000000
# === Square root, logarithm, exponential, power ===
var two = BigFloat.from_string("2", 200)
print(two.sqrt().to_string(20)) # 1.4142135623730950488
print(two.ln().to_string(20)) # 0.69314718055994530942
print(two.exp().to_string(20)) # 7.3890560989306502272
print(two.power(exponent=BigFloat.from_string("0.5", 200)).to_string(20))
# 1.4142135623730950488
# === Pi and the circular functions, at the width you ask for ===
var pi = bigfloat_constants.pi(200)
print(pi.to_string(20)) # 3.1415926535897932385
print(bigfloat_trigonometric.sin(pi / two, 200).to_string(20)) # 1
# === Seven rounding modes, and the neighbours of a value ===
print(two.sqrt().to_string(5, ROUND_DOWN)) # 1.4142
print(two.sqrt().to_string(5, ROUND_UP)) # 1.4143
print(a.next_plus(53).to_string()) # 1.1000000000000003
print(a.next_minus(53).to_string()) # 1.0999999999999999
# === The classes IEEE 754 asks for ===
print(BigFloat.infinity(53).number_class()) # +Infinity
print(BigFloat.nan(53).is_nan()) # TrueRational β exact fractions, with nothing rounded
A quick tour of the Rational (Frac) type. It is a pair of BigInt kept in
lowest terms, so a third is a third and stays one however long you compute
with it. Rounding happens only where you ask for it.
from std.math import ceil, floor
from decimo import Frac, Rational
from decimo.prelude import *
def main() raises:
# === Construction ===
var a = Rational("3/7") # From a ratio of two literals
var b = Rational("1.5") # Read exactly: 3/2
var c = Rational(BigInt(22), BigInt(-8)) # Reduced and signed: -11/4
print(a, b, c) # 3/7 3/2 -11/4
# === Exact arithmetic ===
print(Rational("1/3") + Rational("1/6")) # 1/2
print(a * b) # 9/14
print(a / b) # 2/7
print(Rational("1/3") ** 5) # 1/243
# === Nothing rounds, so the thirds come back whole ===
print(Rational("1/3") * Rational(BigInt(3)) == Rational.one()) # True
# === Comparison ===
print(a < b, a == Rational("6/14")) # True True
# === Rounding, where you ask for it ===
print(round(Rational("7/2")), round(Rational("-7/2"))) # 4 -4
print(round(Rational("22/7"), 4)) # 31429/10000
print(floor(Rational("7/2")), ceil(Rational("7/2"))) # 3 4
# === Conversions ===
print(Rational("22/7").to_bigdecimal(30)) # 3.14285714285714285714285714286
print(Rational(BigDecimal("0.125"))) # 1/8
print(Rational("22/7").to_float()) # 3.142857142857143
# === The closest value with a denominator you can live with ===
print(
Rational(BigDecimal("3.141592653589793")).limit_denominator(
BigInt(1000)
)
) # 355/113Decimal128 β a fixed 128-bit decimal
A quick tour of the main methods of the Decimal128 (Dec128) type.
from decimo.prelude import *
def main() raises:
# === Construction ===
# Decimal128 and Dec128 are aliases
var a = Decimal128("123.45") # From string
var b = Decimal128(123) # From integer
var c = Dec128(123, 2) # Integer with scale (1.23)
var d = Dec128.from_float_scalar(3.14159) # From floating-point
# === Basic Arithmetic ===
print(a + b) # Addition: 246.45
print(a - b) # Subtraction: 0.45
print(a * b) # Multiplication: 15184.35
print(a / b) # Division: 1.0036585365853658536585365854
# === Rounding & Precision ===
print(a.round(1)) # Round to 1 decimal place, half to even: 123.4
print(a.quantize(Dec128("0.01"))) # Format to 2 decimal places: 123.45
print(a.round(0, RoundingMode.ROUND_DOWN)) # Round down to integer: 123
# === Comparison ===
print(a > b) # Greater than: True
print(a == Dec128("123.45")) # Equality: True
print(a.is_zero()) # Check for zero: False
print(Dec128("0").is_zero()) # Check for zero: True
# === Type Conversions ===
print(Float64(a)) # To float: 123.45
print(a.to_int()) # To integer: 123
print(a.to_string()) # To string: "123.45"
print(a.coefficient()) # Get coefficient: 12345
print(a.scale()) # Get scale: 2
# === Mathematical Functions ===
print(Dec128("2").sqrt()) # Square root: 1.4142135623730950488016887242
print(Dec128("100").root(3)) # Cube root: 4.6415888336127788924100763509
print(Dec128("2.71828").ln()) # Natural log: 0.9999993273472820031578910055
print(Dec128("10").log10()) # Base-10 log: 1
print(Dec128("16").log(Dec128("2"))) # Log base 2: 4
print(Dec128("10").exp()) # e^10: 22026.465794806716516957900645
print(Dec128("2").power(10)) # Power: 1024
# === Sign Handling ===
print(-a) # Negation: -123.45
print(abs(Dec128("-123.45"))) # Absolute value: 123.45
print(Dec128("123.45").is_negative()) # Check if negative: False
# === Special Values ===
print(Dec128.PI()) # Ο constant: 3.1415926535897932384626433833
print(Dec128.E()) # e constant: 2.7182818284590452353602874714
print(Dec128.ONE()) # Value 1: 1
print(Dec128.ZERO()) # Value 0: 0
print(Dec128.MAX()) # Maximum value: 79228162514264337593543950335
# === Convenience Methods ===
print(Dec128("123.400").is_integer()) # Check if integer: False
print(a.number_of_significant_digits()) # Count significant digits: 5
print(
Dec128("12.34").to_scientific_string()
) # Scientific notation: 1.234E+1For an interactive session, just type decimo:

A real session: ans, the : settings system, and inline settings
$ decimo
Decimo β an arbitrary-precision calculator π₯
Type ? for help, : for settings, :q to quit.
Precision: 50. Rounding: ROUND_HALF_EVEN.
decimo> 2 ^ 10
1024
decimo> ans / 4
256
decimo> 1/7
0.14285714285714285714285714285714285714285714285714
decimo> :100
Current settings:
Precision : 100
Scientific : off
Engineering : off
Pad : off
Delimiter : (none)
Rounding mode : ROUND_HALF_EVEN
decimo> pi
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068
decimo> sqrt(e) / ln(10) + sin(-1.23) :200 e he delimiter _
-226.458_251_870_114_348_807_514_569_584_297_293_353_150_959_525_480_515_507_901_779_719_167_225_208_528_825_475_488_261_072_148_336_432_171_617_635_953_314_758_797_226_777_458_915_435_649_950_836_584_843_137_886_028_274_720_793_979_517_570_004_978_334_405_953_342_64E-3
decimo> :qThe REPL keeps the last result in ans, lets you define variables
(name = expr), and exposes settings via :-prefixed commands (e.g. :100 for
precision, :s for scientific, :d for ROUND_DOWN). Input is case-insensitive.
Quit with :q, exit, or Ctrl-D.
As an innovative feature, Decimo supports multiple settings in a single line.
They can either be global (persist across calculations) or local (apply only to
the current expression). In the example above, :200 e he delimiter _ means
"evaluate the expression with precision 200 (200), scientific notation with
engineering exponent (e), round half to even (he), and use _ as the digit
delimiter in the output (delimiter _)". The settings apply only to the current
expression and do not affect subsequent calculations.
For one-shot evaluation, pass an expression on the command line, pipe it via stdin, or read from a file:
$ decimo "sqrt(2)" -P 30
1.41421356237309504880168872421
$ echo "1/3" | decimo -P 50
0.33333333333333333333333333333333333333333333333333
$ decimo -F expressions.dm -P 80Useful flags: -P N (precision), -R MODE (rounding), -S / -E (scientific
/ engineering), --pad, --delimiter, --completions {bash,zsh,fish}. Run
decimo --help for the full list.
Everything a decimal program normally touches is there, under the same names:
from decimo import Decimal, Decimal128, getcontext, localcontext, ROUND_FLOOR
getcontext().prec = 28
# The operators, the context, and the methods, as in `decimal`.
Decimal("0.1") + Decimal("0.2") # 0.3, exactly
Decimal(1) / Decimal(7) # to the context precision
Decimal("2.675").quantize(Decimal("0.01"))
divmod(Decimal(17), Decimal(5)) # (3, 2)
# A context you can compute in, without touching the global one.
with localcontext(prec=50):
print(Decimal(2).sqrt())
# Three things `decimal` does not have.
import decimo
decimo.pi(1000) # Chudnovsky with binary splitting
decimo.e(50)
Decimal(2).sqrt(rounding=ROUND_FLOOR) # and exp, ln, log10, correctly rounded
# The fixed-width type for money: 16 bytes that own nothing.
price = Decimal128("19.99")
(price * 3).quantize(Decimal128("0.01")) # 59.97A mixed expression settles in the wider type β Decimal128 + Decimal is a
Decimal, either way round β and the hashes of Decimal, Decimal128,
int, float and decimal.Decimal all agree, so the five are
interchangeable as dictionary keys.
What decimo refuses rather than answering differently: NaN and infinity,
ROUND_05UP, signals and traps, and one context per process rather than per
thread. The full list, with the reasoning, is in
python/README.md.
Financial calculations and data analysis require precise decimal arithmetic that floating-point numbers cannot reliably provide. As someone working in finance and credit risk model validation, I needed a dependable correctly-rounded, fixed-precision numeric type when migrating my personal projects from Python to Mojo.
Since Mojo currently lacks a native Decimal type in its standard library, I decided to create my own implementation to fill that gap.
This project draws inspiration from several established decimal implementations
and documentation, e.g.,
Python built-in Decimal type,
Rust rust_decimal crate,
Microsoft's Decimal implementation,
General Decimal Arithmetic Specification,
etc. Many thanks to these predecessors for their contributions and their
commitment to open knowledge sharing.
Rome wasn't built in a day. Decimo is currently under active development. It has successfully progressed through the "make it work" phase and the "make it right", and is now well into the "make it fast" phase.
The BigInt type is fully implemented and optimized. It is measured against
GMP, timed in C, rather than against CPython's int, which is reached through
the interpreter and so loses on call overhead before the arithmetic starts.
Bug reports and feature requests are welcome! If you encounter issues, please file them here.
The source tree
decimo/
βββ src/ # All source code
β βββ decimo/ # Core library (mojo pre-compiled package)
β β βββ bigdecimal/ # Arbitrary-precision decimal (Decimal)
β β βββ bigint/ # Arbitrary-precision signed integer (BigInt)
β β βββ bigint10/ # Base-10 signed integer (BigInt10)
β β βββ biguint/ # Base-10 unsigned integer (BigUInt)
β β βββ bigfloat/ # Arbitrary-precision binary float
β β βββ mpf/ # The same in MPFR, outside the public API
β β βββ rational/ # Exact rational number (Rational)
β β βββ decimal128/ # 128-bit fixed-precision decimal (Dec128)
β β βββ expression/ # Expression engine behind `decimo.eval()`
β β β βββ tokenizer.mojo # Lexer: expression β tokens
β β β βββ parser.mojo # Shunting-yard: infix β RPN
β β β βββ evaluator.mojo # RPN evaluator using Decimal
β β βββ numerals/ # Numeral systems (e.g. Chinese numerals)
β β βββ toml/ # TOML parser (decimo.toml)
β β βββ ... # Shared utilities (str, errors, rounding)
β βββ cli/ # CLI calculator application
β βββ main.mojo # Entry point (ArgMojo CLI)
β βββ limo/ # Line editor used by the REPL
β βββ calculator/ # Presentation layer (display, io, repl, settings)
βββ python/ # The library packaged for Python (PyPI: decimo)
β βββ decimo_module.mojo # The Python extension, written in Mojo
β βββ src/decimo/ # The `decimo` Python package around it
β βββ tests/ # Checked against the standard library's `decimal`
βββ tests/ # Unit tests (one subfolder per module)
β βββ bigdecimal/
β βββ bigint/
β βββ biguint/
β βββ decimal128/
β βββ expression/ # Expression engine tests
β βββ numerals/ # Numeral system tests
β βββ cli/ # CLI calculator tests
β βββ toml/
βββ benches/ # Benchmarks (one subfolder per module)
βββ docs/ # Documentation and design notes
βββ pixi.toml # Project configuration and tasks
src/decimo/ is a Mojo package β it is compiled with mojo precompile and can
be imported by external projects. The expression engine (decimo.expression),
the numeral systems (decimo.numerals), and the TOML parser (decimo.toml) are
included as subpackages. src/cli/ is an application that consumes the decimo
package and compiles to a standalone binary via mojo build. python/ compiles
the same package into a CPython extension and wraps it in a Python package, and
is what pip install decimo fetches.
After cloning the repo onto your local disk, you can:
- Use
pixi run testto run all tests, orpixi run test <suite>for one suite (pixi run test --listshows them). - Use
pixi run testclito run CLI calculator tests. - Use
pixi run testpyto build the Python extension and run its tests against the standard library'sdecimal. - Use
pixi run benchto run benchmarks. - Use
pixi run benchdocto regenerate docs/benchmarks.md against libmpdec, GMP, CPython and MPFR. Needsmpdecimalandgmpinstalled for the C comparisons; the reference libraries for thepi()table live in the optionalbenchdocenvironment (pixi install -e benchdoc). - Use
pixi run buildclito compile the CLI calculator to a./decimobinary. - Use
pixi run organize_importsto group, sort and de-duplicate the imports in every.mojofile: four blocks separated by a blank line β the Mojo standard library, third-party packages,decimoitself, and modules reached through-I.--checkis what the pre-commit hook runs. A file whose import block holds a comment is left alone and named as skipped;scripts/organize_mojo_imports.py's docstring says why, and why--remove-unusedis opt-in. - Use
pixi run check_import_fixed_pointafter changing the organizer or the Mojo version: it asserts that the organizer andmojo formatdo not undo each other's work. - Use
pixi run docexamplesto run every Mojo example in the documentation and check that it prints what its comment says. A comment that describes the result in prose is left alone; one that names a value is compared with the line that print produced. Each block is a separate compile, so this takes minutes and is not part ofpixi run test. - Use
pixi run one <file.mojo>to run a single file -- a sweep against a reference, a measurement, a probe -- against the working tree.mojo runputs the file's own directory on the import path ahead of-I src, so adecimo.mojocleft beside the file would silently stand in for the source; this clears those first.
If you find Decimo useful, consider listing it in your citations.
@software{Zhu.2026,
author = {Zhu, Yuhao},
year = {2026},
title = {Decimo: An arbitrary-precision integer and decimal library for Mojo},
url = {https://github.com/forfudan/decimo},
version = {0.16.0},
note = {Computer Software}
}This repository and its contributions are licensed under the Apache License v2.0.
The MPF type optionally uses the
GNU MPFR Library (LGPLv3+) and
GMP (LGPLv3+ or GPLv2+) at runtime. Decimo does not
include or distribute any MPFR/GMP source code or binaries β they are loaded via
dlopen only if the user has independently installed them. All other Decimo
types work without any external dependencies. See the NOTICE file
for details.
Footnotes
-
The auxiliary types include a base-10 arbitrary-precision signed integer type (
BigInt10) and a base-10 arbitrary-precision unsigned integer type (BigUInt) supporting unlimited digits6.BigUIntis used as the internal representation forBigInt10andDecimal. β© -
The
BigIntimplementation uses a base-2^64 representation with a little-endian format, where the least significant word is stored at index 0. Each word is aUInt64, allowing for efficient storage and arithmetic operations on large integers. This design choice optimizes performance for binary computations while still supporting arbitrary precision. β© -
Built on the
BigUIntimplementation, Decimal supports arbitrary precision for both the integer and fractional parts, similar todecimalandmpmathin Python,java.math.BigDecimalin Java, etc. β© -
The
Dec128type can represent values with up to 29 significant digits and a maximum of 28 digits after the decimal point. When a value exceeds the maximum representable value (2^96 - 1), Decimo either raises an error or rounds the value to fit within these constraints. For example, the significant digits of8.8888888888888888888888888888(29 eights total with 28 after the decimal point) exceeds the maximum representable value (2^96 - 1) and is automatically rounded to8.888888888888888888888888889(28 eights total with 27 after the decimal point). Decimo'sDec128type is similar toSystem.Decimal(C#/.NET),rust_decimalin Rust,DECIMAL/NUMERICin SQL Server, etc. β© -
The MPFR wrapper that used to be called
BigFloatis still in the source asMPF, atdecimo.mpf.mpf. It is not exported and its interface may change. Using it needs MPFR installed and the C wrapper built withpixi run buildgmp. β© -
The BigInt10 implementation uses a base-10 representation for users (maintaining decimal semantics), while internally using an optimized base-10^18 storage system for efficient calculations. This approach balances human-readable decimal operations with high-performance computing. It provides both floor division (round toward negative infinity) and truncate division (round toward zero) semantics, enabling precise handling of division operations with correct mathematical behavior regardless of operand signs. β©