Skip to content
1stepGrowLearnCompareGrow

Python Numeric Data Types: int, float, Decimal and When to Use Each

Python numeric data types explained: int, float, Decimal, Fraction and complex, why 0.1 + 0.2 is not 0.3, and how to handle money without rounding errors.

Vanshika Nigam

ETL & Data Engineer, Accenture

9 min readUpdated
Share
On this page

Python numeric data types come in three built-in kinds — int, float and complex — plus Decimal and Fraction in the standard library. Use int for counting, float for measurement, and Decimal the moment money is involved. The choices matter more than they look.

Each type has a behaviour that produces wrong numbers without an error. 0.1 + 0.2 gives 0.30000000000000004, round(2.5) gives 2, and a NumPy int64 column can overflow into a large negative number with no warning. This guide is for Python users who handle measurements, data or money. It explains why each happens, the right type for each job, and how to calculate a tax line to the exact paisa.

The numeric types reference also notes that booleans are a subtype of integers, which is why True + True is 2. If you are new to the language, start with the Python basics guide and come back here.

What makes Python integers different?

Python integers have arbitrary precision, so they never overflow:

Python
2 ** 1000        # a 302-digit number, computed exactly
import math
math.factorial(100)   # fine - 158 digits

There is no MAX_INT, which is one of Python's nicer properties.

Readability and bases:

Python
population = 1_400_000_000     # underscores are ignored
0b1010          # 10, binary
0o17            # 15, octal
0xff            # 255, hex
int("ff", 16)   # 255, parse from a string

There is one modern limit, though. Since Python 3.11, converting an integer with more than 4,300 digits to or from a decimal string raises an error, as a defence against denial-of-service attacks. The integer string conversion limit explains how to raise it:

Python
big = 10 ** 5000
str(big)
# ValueError: Exceeds the limit (4300 digits) for integer string conversion;
# use sys.set_int_max_str_digits() to increase the limit

The arithmetic itself is still exact; only the decimal conversion is capped.

The NumPy caveat is important. NumPy uses fixed-width integers for speed, and they wrap silently:

Python
import numpy as np
a = np.array([2**62], dtype=np.int64)
a * 2           # array([-9223372036854775808]) - overflowed, no warning

The NumPy documentation on overflow errors confirms this behaviour. In a data pipeline it produces wrong numbers rather than an error. So if you are multiplying large integer columns, cast to float or Python objects, or check your ranges first.

Why are floats inexact?

Floats are double-precision binary numbers, and therefore approximate:

Python
0.1 + 0.2                 # 0.30000000000000004
0.1 + 0.2 == 0.3          # False

This is not a Python flaw. In binary, 0.1 is a repeating fraction, exactly as 1/3 is in decimal. As a result, every language using IEEE 754 behaves this way. The official tutorial on floating-point arithmetic walks through the details.

Python
from decimal import Decimal
Decimal(0.1)
# Decimal('0.1000000000000000055511151231257827021181583404541015625')

That is the number actually stored.

Never compare floats with ==:

Python
import math
math.isclose(0.1 + 0.2, 0.3)                        # True
math.isclose(1e-13, 0.0)                            # False - relative tolerance only
math.isclose(1e-13, 0.0, abs_tol=1e-12)             # True - tune abs_tol near zero

The second line surprises people. math.isclose uses a relative tolerance by default, so comparisons against zero need abs_tol.

Special values also behave in ways worth knowing:

Python
float("inf")        # inf
float("nan")        # nan

nan = float("nan")
nan == nan          # False - NaN never equals anything, including itself
math.isnan(nan)     # True  - the correct test

That NaN inequality is deliberate, and it is why np.isnan exists — see our NumPy aggregation guide.

How should you handle money in Python?

The rule: if it is currency, use Decimal. The decimal module documentation describes it as preferred for accounting applications with strict equality invariants.

Python
from decimal import Decimal, getcontext, ROUND_HALF_UP

Decimal("0.1") + Decimal("0.2")        # Decimal('0.3') - exact
Decimal("0.1") + Decimal("0.2") == Decimal("0.3")   # True

Construct from strings, not floats, because passing a float imports the error you were trying to avoid:

Python
Decimal(0.1)       # inherits the binary approximation
Decimal("0.1")     # exact

Rounding, explicitly:

Python
price = Decimal("19.995")
price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)   # Decimal('20.00')

getcontext().prec = 28     # precision for the current context (28 is the default)

Note that Python's built-in round uses banker's rounding (round half to even):

Python
round(0.5)       # 0
round(1.5)       # 2
round(2.5)       # 2
round(2.675, 2)  # 2.67 - 2.675 is stored as slightly less than 2.675

The round() documentation calls out that last case explicitly. Half-to-even is statistically unbiased and correct for scientific work. On an invoice, however, it looks like a bug to whoever is checking. Decimal.quantize with ROUND_HALF_UP gives the behaviour a finance team expects.

A worked example with GST-style tax at 18%:

Python
from decimal import Decimal, ROUND_HALF_UP

def line_total(unit_price: str, qty: int, tax_rate: str) -> Decimal:
    subtotal = Decimal(unit_price) * qty
    tax = (subtotal * Decimal(tax_rate)).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_UP
    )
    return subtotal + tax

line_total("19.99", 3, "0.18")     # Decimal('70.76')

The subtotal is 59.97, the tax of 10.7946 rounds to 10.79, and the total is exactly 70.76.

When should you use Fraction?

Use Fraction when you need exact rational arithmetic, such as repeated division that must not drift:

Python
from fractions import Fraction

Fraction(1, 3) + Fraction(1, 6)     # Fraction(1, 2) - exact
float(Fraction(1, 3))                # 0.3333333333333333
Fraction("0.25")                     # Fraction(1, 4)

It is rare in practice, but useful in symbolic or educational contexts where repeated division would otherwise accumulate error.

Does Python support complex numbers?

Yes. Complex numbers are built in, written with a j suffix, and used mostly for signal processing and engineering:

Python
z = 3 + 4j
z.real          # 3.0
z.imag          # 4.0
abs(z)          # 5.0

Does int() round or truncate in Python?

int() truncates toward zero; use round(), math.floor or math.ceil when you want rounding:

Python
int(3.9)        # 3   - truncates toward zero, does not round
int(-3.9)       # -3
round(3.9)      # 4
math.floor(3.9) # 3
math.ceil(3.1)  # 4
True + True     # 2   - bool is a subtype of int

int() truncating rather than rounding is a common source of off-by-one errors, especially when converting a computed average.

Parsing user input safely:

Python
def parse_int(text, default=None):
    try:
        return int(text.strip())
    except (ValueError, AttributeError):
        return default

parse_int(" 42 ")     # 42
parse_int("4.2")      # None - int() will not parse a decimal string

See our exception handling guide for why catching narrowly matters here.

Common mistakes with Python numbers

Most numeric bugs in real code come from a short list:

  • Comparing floats with ==. Use math.isclose, and set abs_tol when comparing with zero.
  • Building Decimal from a float. Decimal(0.1) keeps the binary error; Decimal("0.1") does not.
  • Expecting round() to round halves up. It rounds to even, and float storage can tip a value either way.
  • Assuming NumPy integers behave like Python ints. They overflow and wrap without warning.
  • Using int() to round. It truncates toward zero instead.

Which of the Python numeric data types should you choose?

Choose by what the number represents, not by habit. This table summarises the Python numeric data types covered above:

Need Use Why
Counting, indexing, IDs int exact and unbounded
Measurements, statistics, ML float fast, hardware-backed
Money, tax, anything auditable Decimal exact decimal digits, controllable rounding
Exact ratios Fraction no division error
Signal processing complex built-in real and imaginary parts

The one that matters commercially is the third row. After all, a float rounding error in a financial system is not a rounding error to the person reading the statement.

Next in the series, Python strings covers formatting numbers for display, including thousands separators and percentages.

The Sunday Growth Brief

One email a week: the best new comparisons, a fresh roadmap and the tech news worth your attention.

No spam. Unsubscribe in one click.

Python operators covers arithmetic behaviour in detail. NumPy aggregation covers numeric handling in arrays.

Frequently asked questions

Why does 0.1 + 0.2 not equal 0.3?

Floats are stored in binary, and 0.1 has no exact binary representation, in the same way 1/3 has no exact decimal representation. The stored value is very slightly off, and the small errors compound when you add. This is IEEE 754 behaviour shared by almost every language, not a Python bug, so compare floats with math.isclose instead.

When should I use Decimal instead of float?

Whenever the exact decimal value matters and someone could audit it: money, tax, invoices and interest. Decimal is slower than float, but it represents decimal fractions exactly and lets you control rounding explicitly. Always build it from a string such as Decimal('0.10'), because building it from a float copies the binary error across.

Do Python integers overflow?

No. Python ints grow to whatever size is needed, limited only by memory. However, NumPy arrays use fixed-width integers, so int64 does overflow, and it silently wraps round, which is a real source of bugs in data pipelines. Separately, converting an int with more than 4,300 digits to a decimal string raises ValueError by default.

How should I round numbers in Python?

Be aware that round() uses banker's rounding, so round(0.5) is 0 and round(1.5) is 2. That is correct for statistics and surprising for invoices. Also, round(2.675, 2) gives 2.67 because 2.675 is stored as a slightly smaller float. For money, use Decimal.quantize with an explicit rounding mode such as ROUND_HALF_UP.

Written by

Vanshika Nigam

ETL & Data Engineer, Accenture

ETL developer at Accenture working with Informatica, Snowflake and DBT, specialising in data mapping, cleansing and pipeline performance.

SQLETL pipelinesSnowflakeData analysis

Related reading

Free weekly newsletter

Get the shortlist before everyone else

Every Sunday we send one email with the week’s sharpest course comparison, a career roadmap worth stealing, and the tech news and hiring signals we’re watching.

  • No fluff, ever
  • Unsubscribe anytime
  • 5-minute read

We never share your address. One click to leave.