Why this matters
Nearly every program counts, measures or calculates something. Python gives you two everyday number types, int and float, and they behave very differently under the hood. Ints are exact and can grow as large as memory allows. Floats are fast approximations with about 16 significant digits, and they produce surprises like 0.1 + 0.2 printing 0.30000000000000004. Knowing why lets you choose the right type, compare numbers safely, and not panic at strange last digits.
int: whole numbers of any size
An int is an exact whole number, positive, negative or zero:
print(7, -3, 0)
print(1_000_000) # underscores are allowed for readability
print(2 ** 100)
7 -3 0
1000000
1267650600228229401496703205376
** is the power operator. In C or Java, a 64-bit integer overflows past 9,223,372,036,854,775,807 and silently wraps around to a negative number. Python ints never overflow. Underneath, CPython stores an int as an array of "digits" in base 2**30, and allocates more of them as the number grows:
small int: [ type=int | refcount | size=1 | digit0 ]
huge int: [ type=int | refcount | size=5 | digit0 digit1 digit2 digit3 digit4 ]
The cost is speed and memory: arithmetic on huge numbers takes longer than on small ones, and even the int 1 is a full object of about 28 bytes. In exchange, integer results are always exactly right. Python 3.11+ does refuse to convert integers with more than 4300 digits to strings by default (a guard against denial-of-service attacks), but arithmetic itself has no limit.
float: binary floating point
A float represents real numbers approximately, using the IEEE 754 double precision format that the CPU supports in hardware:
print(3.14, -0.5, 2.0)
print(1.5e3) # scientific notation: 1.5 times 10**3
print(10 / 4) # / always produces a float
3.14 -0.5 2.0
1500.0
2.5
A double uses 64 bits: 1 sign bit, 11 exponent bits and 52 fraction bits. The value is like scientific notation, but in base 2:
value = (-1)^sign x 1.fraction (binary) x 2^(exponent - 1023)
This gives about 15-17 significant decimal digits and a range up to about 1.8e308. It is the same format used by C's double, JavaScript numbers and spreadsheets.
Why 0.1 is not exact
Just as 1/3 cannot be written exactly in decimal (0.3333...), 1/10 cannot be written exactly in binary: it is 0.0001100110011... repeating forever. The float stores the nearest 53-bit binary fraction. Python prints the shortest decimal string that maps back to the same float, which is why 0.1 normally displays as 0.1, hiding the error. Arithmetic can expose it:
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(1.1 * 3)
0.30000000000000004
False
3.3000000000000003
This is not a Python bug; every language using IEEE 754 does the same. The errors are tiny (relative error around 1e-16), but they mean you should never compare floats with == after arithmetic.
Comparing floats: math.isclose
The math module (part of the standard library) has isclose:
import math
print(math.isclose(0.1 + 0.2, 0.3))
print(math.isclose(1.0, 1.001))
print(math.isclose(1.0, 1.001, rel_tol=0.01))
True
False
True
By default two numbers are "close" if they differ by at most one part in a billion (rel_tol=1e-09) relative to their size. You can pass a looser tolerance. Comparing with zero needs abs_tol, because a relative tolerance of zero is zero: math.isclose(1e-20, 0.0, abs_tol=1e-12).
When floats are the wrong tool
For money, where every cent must be exact, use integers counting cents, or the standard library's decimal.Decimal, which does decimal arithmetic exactly: Decimal("0.1") + Decimal("0.2") == Decimal("0.3") is True. For exact fractions there is fractions.Fraction. Floats are right for measurements and science, where inputs are approximate anyway.
Mixing ints and floats
When an operation combines an int and a float, the int is converted to float first and the result is a float:
print(3 + 0.5)
print(2 * 1.0)
print(7 / 7) # true division always gives float
print(7 // 2) # floor division of ints stays int
3.5
2.0
1.0
3
round(), int() and friends
print(round(3.7)) # nearest int
print(round(3.14159, 2)) # 2 decimal places, still a float
print(round(2.5), round(3.5))
print(int(3.9)) # int() truncates toward zero
print(int(-3.9))
4
3.14
2 4
3
-3
Two surprises here:
rounduses round half to even: exact ties go to the even neighbor, soround(2.5)is 2. This reduces bias when rounding many numbers.int(x)on a float truncates (chops off the fraction toward zero); it does not round.math.floorrounds down andmath.ceilrounds up:math.floor(-3.9)is-4.
Rounding for display is a different job, done with formatting (f"{x:.2f}"), covered in the strings module.
Special values: inf and nan
Floats include infinities and "not a number":
big = 1e308 * 10
print(big)
print(-big)
print(big - big)
print(float("nan") == float("nan"))
inf
-inf
nan
False
Overflowing the float range gives inf rather than an error. Undefined results like infinity minus infinity give nan, and nan is not equal to anything, not even itself. Use math.isinf and math.isnan to test for them. Dividing by zero is different: Python raises ZeroDivisionError for 1 / 0 and 1.0 / 0.0 instead of returning inf.
Common mistakes
==on computed floats. Usemath.isclose.- Expecting
int()to round.int(2.99)is 2. Useround(). - Floats for money.
0.1 + 0.2cents add up to a wrong total after enough transactions. Use integer cents orDecimal. - Huge float results.
2.0 ** 10000raisesOverflowError: (34, 'Numerical result out of range'), while2 ** 10000(ints) works fine. - Converting long ints to text.
str(10 ** 5000)raisesValueError: Exceeds the limit (4300 digits) for integer string conversion;sys.set_int_max_str_digitsraises the limit if you really need it.
Recap
intis exact with unlimited size, stored as a variable-length array of digits.floatis a 64-bit IEEE 754 double: about 16 significant digits, fast, approximate.- Decimal fractions like 0.1 are not exact in binary, so compare floats with
math.isclose, never==. - Mixing int and float gives a float;
/always gives a float. rounduses half-to-even;int()truncates toward zero;infandnanexist andnan != nan.
Going further: the Python tutorial chapter "Floating-Point Arithmetic: Issues and Limitations" is short and excellent. Try (0.1).as_integer_ratio() and (0.1).hex() to see the exact value stored for 0.1.