# 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.

- **Author:** Vanshika Nigam — ETL & Data Engineer, Accenture (https://www.1stepgrow.com/authors/vanshika-nigam/)
- **Published:** Jul 16, 2026 · **Updated:** Sep 17, 2026
- **Topic:** Python & Programming · **Format:** Guide · **Read time:** 9 min
- **Canonical URL:** https://www.1stepgrow.com/articles/python-numeric-data-types/

## Key takeaways

- Python ints are arbitrary precision — they never overflow, unlike NumPy's fixed-width integers.
- Floats are binary approximations; 0.1 cannot be represented exactly and never will be.
- Use decimal.Decimal, constructed from strings, for money and anything where the exact decimal value is contractual.
- Never compare floats with ==. Use math.isclose.
- Built-in round() rounds halves to even, so use Decimal.quantize with an explicit rounding mode for invoices.

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](https://docs.python.org/3/builtins/stdtypes.html#typesnumeric) 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](https://www.1stepgrow.com/articles/python-basics) 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](https://docs.python.org/3/builtins/stdtypes.html#int-max-str-digits) 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](https://numpy.org/doc/stable/user/basics.types.html#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](https://docs.python.org/3/tutorial/floatingpoint.html) 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`](https://docs.python.org/3/library/math.html#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](https://www.1stepgrow.com/articles/numpy-aggregation-functions).

## How should you handle money in Python?

The rule: **if it is currency, use `Decimal`.** The [decimal module documentation](https://docs.python.org/3/library/decimal.html) 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](https://docs.python.org/3/builtins/functions.html#round) 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](https://www.1stepgrow.com/articles/python-exception-handling) 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](https://www.1stepgrow.com/articles/python-strings) covers formatting numbers for display, including thousands separators and percentages.



## Related reading

[Python operators](https://www.1stepgrow.com/articles/python-operators) covers arithmetic behaviour in detail. [NumPy aggregation](https://www.1stepgrow.com/articles/numpy-aggregation-functions) 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.

---
_Source: 1stepGrow (https://www.1stepgrow.com/articles/python-numeric-data-types/). Cite with the title, "1stepGrow" and a link._
