Learn by reading through in order

decimal and fractions — Precise Arithmetic Without Float Errors

Learn Python's decimal and fractions modules from the ground up. Covers the 0.1 + 0.2 != 0.3 problem with float, exact monetary calculation with Decimal (and why you build Decimals from strings), arithmetic with auto-reducing Fraction, and choosing among the three numeric types — with runnable practice exercises.

This article covers the two modules for the cases where `float` causes problems. decimal.Decimal does exact base-10 arithmetic, which makes it essential for monetary calculations; fractions.Fraction keeps the numerator and denominator as integers so it can compute with reduced fractions exactly. Both exist to remove the inevitable rounding errors of float.

Float Rounding Errors and decimal.Decimal — Don't Use Float for Precise Math

Python's float is represented internally in binary, so decimal fractions like `0.1` that don't divide cleanly in binary carry a tiny rounding error. The most famous example is `0.1 + 0.2 == 0.3` returning `False`. For graphics or scientific computing the error is negligible, but in financial sums even a small drift becomes critical.

That's why decimal.Decimal exists. It's represented internally in base 10, so building one from the string "0.1" lets you compute without error.

float vs. Decimal
float0.1 + 0.2→ 0.30000000000000004rounding errorDecimalDecimal('0.1') + Decimal('0.2')→ Decimal('0.3')no error
float is fast (CPU instructions) but introduces rounding errors with values like 0.1. Decimal uses a base-10 internal representation, so it computes exactly as the input string says, but is slower than float. For money and tax processing, choose Decimal.
from decimal import Decimal

# float rounding error
print(0.1 + 0.2)              # 0.30000000000000004
print(0.1 + 0.2 == 0.3)       # False  ← against intuition!

# Build Decimal from a string (building from float inherits the float's error)
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b)                  # 0.3
print(a + b == Decimal("0.3"))  # True

# A monetary example
price = Decimal("1980")
tax_rate = Decimal("0.10")
total = price * (Decimal("1") + tax_rate)
print(total)                  # 2178.00

Build Decimal from a string, not from a float

If you pass a float — Decimal(0.1) — you're handing in a value that already carries a rounding error, so you end up with a Decimal that still has the float's error. Always pass a string: Decimal("0.1"). It's hard to spot in tests and is the classic pitfall that produces a one-cent drift in production for the first time.

First observe the float rounding error, then switch to Decimal for an exact tax-included calculation.

① Import Decimal from the decimal module.

② Compute 0.1 + 0.2 with float and print it as float: ◯◯ (you should see the error).

③ Print the result of 0.1 + 0.2 == 0.3 as float equal?: ◯◯ (it returns False, against intuition).

④ Compute 0.1 + 0.2 with Decimal and print it as Decimal: ◯◯ (no error this time).

⑤ Define a pre-tax price of 1980 and a 10% tax rate as Decimals, compute the tax-included total, and print it as total: ◯◯.

Python Editor

Run code to see output

fractions.Fraction — Keeping Fractions as Fractions

Fraction is a type that stores the numerator and denominator as integers for arithmetic. Build one with Fraction(1, 3), and subsequent additions, subtractions, and multiplications return a reduced-fraction result. As a float, 1/3 is the approximation 0.3333333333333333, but with Fraction it stays as "one-third" itself.

Fraction at a Glance
Fraction(1, 3)+ Fraction(1, 6)Fraction(1, 2)(auto-reduced)
Build with Fraction(numerator, denominator) — an integer-ratio type. Results of + / - / * are also Fraction, and they're automatically reduced. You can convert to float with float(f), but that introduces rounding error.
TypeInternal representationBest fit
intIntegerCounts and tallies (no error)
floatBinary floating-pointScientific computing, graphics (fast, with error)
DecimalBase-10 representationMoney and tax (when base-10 precision matters)
FractionNumerator and denominator (integers)Ratios and probabilities (when you want a reduced form)

Fraction objects expose attributes for the numerator and denominator. f.numerator returns the numerator and f.denominator returns the denominator — both after reduction. For example, Fraction(2, 6) is normalized internally to Fraction(1, 3), so .numerator is 1 and .denominator is 3.

Attribute / MethodMeaningExample
f.numeratorNumeratorFraction(1, 3).numerator → 1
f.denominatorDenominatorFraction(1, 3).denominator → 3
float(f)Convert to floatfloat(Fraction(1, 2)) → 0.5

Add two fractions and access the attributes of the result. Compute 1/3 + 1/6 while keeping it as a Fraction, and confirm that the answer is automatically reduced.

① Import Fraction from the fractions module.

② Build 1/3 and 1/6 as Fractions.

③ Add the two and print the result as sum: ◯/◯ (it should be auto-reduced).

④ Read the numerator and denominator off the result via attributes, and print them as numerator: ◯ denominator: ◯.

⑤ Convert the result to float and print it as as float: ◯.◯.

Python Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1Which is the most appropriate choice when you can't tolerate even a one-cent drift in monetary calculations?

Q2When constructing a Decimal, which is the correct way that doesn't introduce error?

Q3Which one do you reach for when you want to compute ratios or probabilities as reduced fractions?