The decimal and fractions Modules
When binary
floatis the wrong number system, Python’s standard library offers two exact alternatives.decimal.Decimalis exact decimal floating-point — base-10, soDecimal('0.1') + Decimal('0.2')is exactlyDecimal('0.3'), with user-controlled precision and rounding, which is why money and finance use it.fractions.Fractionis an exact rational — a numerator/denominator pair kept in lowest terms, soFraction(1, 3)is genuinely one-third with no rounding at all. Both trade speed for exactness;Decimalis backed by a fast C library (libmpdec) whileFractionis pure Python built on arbitrary-precision integers. The single most important idea: choose the number system that matches the domain — decimal for human/financial quantities, rationals for exact ratios — instead of forcing everything through binaryfloatand hoping the rounding error stays small. (Per thedecimalandfractionsdocumentation, verified against CPython 3.14.5.)
Mental Model
A binary float ([[CPython Float Internals]]) stores a number as sign × mantissa × 2^exponent. Tenths like 0.1 are not finite sums of powers of two, so they are approximated — 0.1 + 0.2 lands on 0.30000000000000004. Decimal keeps the same floating-point idea but in base 10: a sign, a string of decimal digits, and a power-of-ten exponent, so anything with a finite decimal expansion is stored exactly. Fraction goes further still — it stores two integers p/q and represents every rational exactly, including 1/3, which has no finite decimal form at all.
flowchart TD Q["a number you need"] --> D1{"finite decimal?<br/>(money, prices)"} D1 -->|yes, need control| DEC["Decimal<br/>sign · digits · 10^exp<br/>exact base-10"] D1 -->|no, it's a ratio| FR{"exact ratio?<br/>(1/3, 22/7)"} FR -->|yes| FRAC["Fraction<br/>p / q in lowest terms<br/>exact rational"] FR -->|"no, just fast & approx"| FLT["float<br/>sign · mantissa · 2^exp<br/>fast, inexact"] DEC -.->|backed by| MP["libmpdec (C)<br/>arbitrary precision"] FRAC -.->|backed by| INT["Python int<br/>arbitrary precision"]
Figure: a decision tree from “what kind of number” to the representation. Insight: Decimal and Fraction both buy exactness by abandoning the fixed-width binary mantissa — Decimal works in base 10 with controllable precision, Fraction keeps two big integers — and each is built on a different exact-integer engine.
decimal.Decimal — exact decimal floating-point
Decimal implements the General Decimal Arithmetic Specification (decarith — the standard the decimal docs name as their basis, covering arithmetic operations and exceptional conditions), arithmetic “based on a model familiar to people” — it works the way decimal arithmetic is taught in school (decimal docs). The headline property:
>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')
>>> 0.1 + 0.2
0.30000000000000004- Line 2: each
Decimal('0.1')andDecimal('0.2')stores its value exactly in base 10, so their sum is exactlyDecimal('0.3')— the fix for the float trap documented in[[Floating Point Pitfalls in Python]]. - Line 4: the binary
floatversion exposes the rounding error inherent in base-2 storage ([[CPython Float Internals]]).
Construction — and the float trap, again
How you construct a Decimal matters enormously:
>>> Decimal('0.1') # from a string: exact
Decimal('0.1')
>>> Decimal(0.1) # from a float: inherits the float's error
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal(10) # from an int: exact
Decimal('10')Decimal('0.1')parses the decimal text and is exactly one tenth.Decimal(0.1)converts the already-imprecise binaryfloat0.1, faithfully reproducing its full binary value — the long tail of digits is the actual stored value of the float0.1. This is correct behavior (lossless conversion of the float), but it surprises people: never feed afloattoDecimalif you wanted the decimal you typed. Use a string.Decimal.from_float(0.1)is the explicit spelling of the float conversion; new in 3.14,Decimal.from_number()accepts a float/int/Decimal (but not a string) as a uniform alternative constructor (3.14 whatsnew, gh-121798).
Decimal also preserves significance: trailing zeros are kept because they signal precision. Decimal('1.30') + Decimal('1.20') is Decimal('2.50'), not Decimal('2.5') — the two-decimal-place result carries the information that the inputs were known to the hundredths.
The C accelerator: _decimal / libmpdec
The module ships in two implementations. The default is _decimal, a C extension that integrates libmpdec, described by its author as “a C library for correctly-rounded arbitrary precision decimal floating point arithmetic” (libmpdec). Per the decimal module’s own FAQ, this C version “uses Karatsuba multiplication for medium-sized numbers and the Number Theoretic Transform for very large numbers” (decimal FAQ). There is also a pure-Python fallback, _pydecimal (shipped as Lib/_pydecimal.py), used only when the C version is unavailable. The C accelerator landed in Python 3.3 and is dramatically faster than the old pure-Python module for large numbers.
Resolved (2026-06-01)
Benchmarked directly on this 3.14.5 build: a mixed multiply/divide loop (
prec=50, 50k iterations ofs += a*b; s -= a/b) ran roughly ~30–45× faster under the C_decimal(libmpdec) module than under the pure-Python_pydecimalfallback across repeated runs (C ≈70–120 ms vs pure ≈3.2–3.6 s). That lands squarely in the “10× to 100×” range older CPython docs used to quote — the exact multiple is workload-dependent (heavily division- or high-precision-bound code skews higher), but the order of magnitude is firmly confirmed, not merely the direction.
The Context — precision, rounding, traps
A Decimal’s value is exact, but every arithmetic operation happens under a context that controls how the result is rounded and what abnormal conditions do. Inspecting the default:
>>> from decimal import getcontext
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
capitals=1, clamp=0, flags=[],
traps=[Overflow, DivisionByZero, InvalidOperation])prec=28: results are rounded to 28 significant digits after each operation. Crucially, precision applies to operation results, not to construction —Decimal('3.1415926535')keeps all its digits, butDecimal('3.1415926535') + Decimal('2.7182818285')is rounded to the context precision.rounding=ROUND_HALF_EVEN: the default rounding mode is “round half to even” (banker’s rounding) — ties round to the nearest even last digit, so0.5→0,1.5→2,2.5→2. This avoids the upward bias of always rounding halves away from zero, which matters when summing many rounded values. The eight modes (decimal docs, “Rounding modes”) areROUND_CEILING(toward +∞),ROUND_FLOOR(toward −∞),ROUND_UP(away from zero),ROUND_DOWN(toward zero),ROUND_HALF_UP(ties away from zero — “school” rounding),ROUND_HALF_DOWN(ties toward zero),ROUND_HALF_EVEN(ties to even, the default), andROUND_05UP(round away from zero only if the last digit would be 0 or 5).traps=[Overflow, DivisionByZero, InvalidOperation]: the default trap set. Each abnormal condition is a signal with both a flag (always set when the condition occurs) and a trap enabler (if on, the condition raises an exception instead of returning a special value). With the default traps,Decimal(1)/Decimal(0)raisesDivisionByZero; turn the trap off and it returnsDecimal('Infinity')while merely setting the flag. The full signal set includesClamped,InvalidOperation,DivisionByZero,Inexact(a rounding lost information),Rounded(rounding occurred at all),Subnormal,Overflow,Underflow, andFloatOperation(mixing float and Decimal — opt-in since 3.3).
Setting Inexact as a trap is a powerful trick: it turns any silent rounding into an exception, so a financial pipeline can prove it never lost a cent rather than hope.
Contexts are managed with getcontext()/setcontext() and, for scoped changes, localcontext():
>>> from decimal import localcontext, Decimal
>>> with localcontext() as ctx:
... ctx.prec = 42
... Decimal(1) / Decimal(7)
...
Decimal('0.142857142857142857142857142857142857142857')
>>> Decimal(1) / Decimal(7) # outside: back to prec=28
Decimal('0.1428571428571428571428571429')- The
with localcontext() as ctx:block gives a copy of the current context; settingctx.prec = 42affects only operations inside the block, and the previous context is restored on exit — the safe way to do a high-precision calculation without disturbing the rest of the program. Since 3.11 you can also pass settings directly:localcontext(prec=42). Each thread has its own current context, so this is concurrency-safe.
For fixing a result to a set number of decimal places (the everyday “round to cents” operation), use quantize:
>>> Decimal('7.325').quantize(Decimal('0.01')) # default ROUND_HALF_EVEN
Decimal('7.32')quantize(Decimal('0.01'))rounds to two decimal places. With banker’s rounding the tie...25at the hundredths rounds the2(already even) down, giving7.32. Passrounding=ROUND_HALF_UPto get the “always round .5 up” behavior most invoices want.
Why finance uses Decimal
Money is defined in decimal units (cents, pennies) and regulated rounding rules are decimal rules. A binary float cannot even represent $0.10 exactly, so a column of float prices accumulates error and a rounding standard like “round half up to the nearest cent” cannot be applied faithfully. Decimal represents the amounts exactly and lets you set the exact rounding mode the jurisdiction or accounting standard mandates — and, with Inexact/Rounded traps, can assert that no unintended rounding ever happened. That combination — exact representation plus controllable, auditable rounding — is why Decimal is the standard choice for currency, billing, and accounting.
fractions.Fraction — exact rationals
Fraction represents a rational number as an exact numerator/denominator pair, automatically reduced to lowest terms with a positive denominator (fractions docs):
>>> from fractions import Fraction
>>> Fraction(16, -10)
Fraction(-8, 5)
>>> Fraction(1, 3) + Fraction(1, 6)
Fraction(1, 2)
>>> Fraction(1, 3) * 3
Fraction(1, 1)Fraction(16, -10): on construction the constructor divides both parts by their greatest common divisor (gcd(16, 10) = 2) and moves the sign to the numerator, yielding-8/5. Since Python 3.9 it usesmath.gcdfor this normalization.Fraction(1, 3) + Fraction(1, 6)is computed exactly over a common denominator and reduced to1/2— no rounding, unlike the float1/3 + 1/6. The numerator and denominator are ordinary Pythonints, so they are arbitrary-precision ([[CPython Integer Internals]]); aFractioncan hold ratios of enormous integers exactly.Fraction(1, 3) * 3is exactlyFraction(1, 1)— the very thingfloatcannot do, since1/3is not representable in binary.
Construction from many sources
>>> Fraction('3/7')
Fraction(3, 7)
>>> Fraction('-.125')
Fraction(-1, 8)
>>> Fraction(2.25) # from a float that happens to be exact
Fraction(9, 4)
>>> Fraction(1.1) # from an inexact float
Fraction(2476979795053773, 2251799813685248)
>>> Fraction(Decimal('1.1')) # from a Decimal: exact
Fraction(11, 10)- A string like
'3/7'or decimal text'-.125'is parsed exactly. Fraction(2.25)gives9/4because2.25is exactly representable in binary (it is9 × 2^-2).Fraction(1.1)exposes the same float trap asDecimal(0.1): it faithfully converts the actual binary value of the float1.1, which is not11/10, so you get a giant power-of-two denominator. To get11/10, construct from a string or aDecimal.Fraction(Decimal('1.1'))is exactly11/10, because theDecimalwas itself exact.
The explicit alternative constructors are Fraction.from_float, Fraction.from_decimal, and — new in 3.14 — Fraction.from_number, which accepts any int/Rational/float/Decimal or object exposing as_integer_ratio() (3.14 whatsnew, gh-121797). Also new in 3.14, the Fraction(...) constructor itself accepts any object with an as_integer_ratio() method.
limit_denominator — the best rational approximation
limit_denominator(max_denominator=1000000) finds the closest Fraction whose denominator does not exceed the bound — the standard way to recover a “nice” fraction from a messy float:
>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
>>> Fraction(0.1).limit_denominator()
Fraction(1, 10)limit_denominator(1000)on π’s decimal expansion returns355/113— the famous Milü approximation accurate to six decimals, the best rational with denominator ≤ 1000.Fraction(0.1).limit_denominator()cleans up the giant power-of-two fraction from the inexact float back to the intended1/10, by finding the simplest fraction within the default bound that matches.
The performance trade-off
Fraction is pure Python and every operation does gcd reductions on potentially growing integers, so it is much slower than float and slower than Decimal for heavy arithmetic; repeated additions can make numerators and denominators balloon before reduction. It is the right tool for exact symbolic-ish ratio work — probability, exact geometry, unit conversions, test oracles — not for number-crunching inner loops. as_integer_ratio() and is_integer() (the latter added 3.12) round out the API, and arithmetic mixing a Fraction with a float deliberately down-converts to float (you opted out of exactness), whereas mixing with int or Decimal-via-construction stays exact.
Choosing among Decimal, Fraction, and float
float is fast, fixed-width, hardware-accelerated, and inexact — correct for measurements, scientific computing, and anything where a relative error around 10⁻¹⁶ is fine and speed matters. Decimal is exact in base 10 with controllable precision and rounding — correct for money and any human-facing decimal quantity where the rounding rule is a requirement, not an afterthought. Fraction is exact for all rationals including those with no finite decimal form (1/3) — correct when you need true ratios and can pay the speed cost. A useful rule: if the problem is stated in dollars and cents, use Decimal; if it is stated as a ratio of whole things, use Fraction; if it is a physical measurement and you need throughput, use float — and if float is silently giving you wrong money, that is the bug [[Floating Point Pitfalls in Python]] warns about, and the fix is Decimal.
See Also
[[The struct Module and Binary Layout]]— sibling §18 note; binary layout and IEEE-float packing, the opposite concern from exact arithmetic.[[CPython Float Internals]]— the binaryfloatwhose imprecision motivates both modules.[[Floating Point Pitfalls in Python]]— the concrete float trapsDecimalfixes.[[CPython Integer Internals]]— the arbitrary-precisionintthatFractionnumerators/denominators are built from.[[Python Internals MOC]]— §18 Selected Standard Library Internals.