Values, types and names · about 40 min

Integers and floats

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:

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

Recap

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.

Exercises

Exercise 1 · quiz

Does 0.1 + 0.2 equal 0.3?

What does print(0.1 + 0.2 == 0.3) print?

Answer
  • False

Neither 0.1, 0.2 nor 0.3 can be stored exactly in binary floating point. The stored value for 0.1 + 0.2 is 0.30000000000000004440892098500626..., while the stored value for 0.3 is 0.299999999999999988897769753748..., so they differ in the last bit. Every IEEE 754 machine gives the same answer: the result is deterministic, just not what decimal intuition expects. Compare floats with math.isclose.

Exercise 2 · write code

Ints never overflow

Read a whole number n (0 or more) and print two lines: the value of 2 to the power n, and how many decimal digits that number has. For input 10:

1024
4

Use ** for powers. To count digits, convert the number to a string with str() and take its length with len().

File: main.py

Starting point

n = int(input())
print(2 * n)

Example

input

10

expected output

1024
4

Example

input

0

expected output

1
1
Hint 1

2 ** n is two to the n-th power; 2 * n is just doubling.

Hint 2

len(str(big)) counts the characters in the decimal representation.

Reference solution
n = int(input())
big = 2 ** n
print(big)
print(len(str(big)))

Exercise 3 · write code

Comparing floats properly

Read three numbers a, b and c (one per line) as floats. Print whether a + b is exactly equal to c, and whether it is close to c according to math.isclose with its default tolerances, in exactly this format (for inputs 0.1, 0.2, 0.3):

exact: False
close: True

You need import math at the top of the program.

File: main.py

Starting point

a = float(input())
b = float(input())
c = float(input())
print("exact:", a + b == c)

Example

input

0.1
0.2
0.3

expected output

exact: False
close: True

Example

input

1.5
2.5
4

expected output

exact: True
close: True
Hint 1

math.isclose(x, y) returns True when x and y agree to about 9 significant digits.

Hint 2

The second line is print("close:", math.isclose(a + b, c)).

Reference solution
import math

a = float(input())
b = float(input())
c = float(input())
print("exact:", a + b == c)
print("close:", math.isclose(a + b, c))

Exercise 4 · quiz

round() and ties

What do round(2.5) and round(3.5) return in Python 3? Type the two results separated by a space, for example 3 4.

Answer

Python's round uses round half to even (banker's rounding): an exact tie goes to the nearest even integer, so 2.5 becomes 2 and 3.5 becomes 4. This avoids the upward bias of always rounding halves up when you round many values. Note that ties are rare with floats: round(2.675, 2) gives 2.67 because 2.675 is actually stored as 2.67499999...