Numbers

Python's numeric tower — int, float, and complex — comes with arbitrary-precision integers, readable literal syntax, and floating-point quirks inherited from IEEE-754. Getting comfortable with all three is foundational to every numeric computation you'll write.

Python provides three built-in numeric types: int for whole numbers of unbounded size, float for IEEE-754 double-precision real numbers, and complex for numbers with real and imaginary parts. This topic covers how to write numeric literals in every common base, how Python's arbitrary-precision integers differ from fixed-width integers in other languages, and where floating-point arithmetic can surprise you.

int: Arbitrary Precision

Python integers are not limited to a fixed number of bits (like a 32-bit or 64-bit int in C or Java). An int can grow to represent numbers as large as available memory allows, and Python automatically handles the extra bookkeeping — there is no separate "long" type to opt into, and no silent overflow.

CPython implementation detail: internally, CPython represents large ints as an array of "digits" in a variable-length structure; the exact representation and the small-integer cache (typically -5 to 256) are implementation details, not language guarantees.

big = 2 ** 200
print(big)
print(big + 1)          # keeps growing without wrapping around
factorial_50 = 1
for i in range(1, 51):
    factorial_50 *= i
print(factorial_50)      # exact, no overflow

float: IEEE-754 Double Precision

Python's float type implements the IEEE-754 double-precision binary floating-point standard — the same format used by most modern languages. This gives roughly 15-17 significant decimal digits of precision, but it also means many decimal fractions (like 0.1) cannot be represented exactly in binary, only approximated.

print(0.1 + 0.2)              # 0.30000000000000004 -- not exactly 0.3
print(0.1 + 0.2 == 0.3)       # False
import math
print(math.isclose(0.1 + 0.2, 0.3))   # True -- the correct way to compare floats

Floating-Point Limitations in Depth

This isn't a Python bug — it's a direct consequence of representing base-10 fractions in binary floating-point, shared by virtually every language that uses IEEE-754 floats. The fix is never to compare floats for exact equality; instead use a tolerance-based comparison such as math.isclose(), or work in fixed-point/decimal arithmetic (the decimal module) when exactness matters, such as for money.

The Decimal and Fractions modules are covered in their own dedicated topics; mentioned here only as the standard-library escape hatch for exact decimal arithmetic.

from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2"))   # Decimal('0.3') -- exact

# Also watch for magnitude limits:
print(1e308 * 10)    # inf -- overflow to infinity
print(float("nan") == float("nan"))   # False -- NaN is never equal to anything, even itself

complex Numbers

Python has built-in support for complex numbers, written with a trailing j (or J) suffix for the imaginary part. A complex number exposes .real and .imag attributes and supports the usual arithmetic operators.

z = 2 + 3j
w = 1 - 1j
print(z + w)          # (3+2j)
print(z * w)          # (5+1j)
print(z.real, z.imag)  # 2.0 3.0
print(abs(z))           # magnitude: sqrt(2**2 + 3**2)

Numeric Literals

Python supports a variety of literal forms for writing numbers directly in source code, beyond plain decimal digits.

decimal_lit = 42
float_lit = 3.14
hex_lit = 0x1A          # 26 in decimal
oct_lit = 0o17          # 15 in decimal
bin_lit = 0b1010        # 10 in decimal
sci_lit = 6.022e23      # scientific notation
imag_lit = 5j            # imaginary literal
print(decimal_lit, float_lit, hex_lit, oct_lit, bin_lit, sci_lit, imag_lit)

Hexadecimal, Octal, and Binary Literals

Integer literals can be written in bases other than 10 using a 0x/0X (hex, base 16), 0o/0O (octal, base 8), or 0b/0B (binary, base 2) prefix. Internally these are all just int objects — the base is only a notational convenience at the source-code level; once parsed, there's no difference between 0x1A, 0o32, 0b11010, and 26.

print(0x1A == 0o32 == 0b11010 == 26)   # True -- all the same int object value

# Converting the other direction, from int to a string in each base:
print(hex(26), oct(26), bin(26))         # '0x1a' '0o32' '0b11010'

Scientific Notation

Scientific notation writes a float as mantissa e/E exponent, meaning mantissa × 10^exponent. It's convenient for very large or very small magnitudes.

speed_of_light = 2.998e8     # 299,800,000.0
planck_constant = 6.626e-34   # a very small number
print(speed_of_light, planck_constant)
print(type(speed_of_light))    # <class 'float'> -- scientific notation always produces a float

Underscores in Numeric Literals

Since Python 3.6, you can insert underscores between digits in numeric literals purely for readability — they are ignored by the parser and have no effect on the resulting value. This is especially useful for large numbers.

Underscores must appear between digits only — not leading, trailing, or doubled (_100, 100_, and 1__000 are all syntax errors).

population = 7_900_000_000
binary_flags = 0b_1010_0101
precise_pi = 3.14159_26535
print(population, binary_flags, precise_pi)
print(population == 7900000000)   # True -- underscores don't change the value

Numeric Precision and Conversions Between Types

Mixing int and float in an expression promotes the result to float. Converting a float to int with int() truncates toward zero (it does not round). Very large ints converted to float can lose precision, since float has only about 15-17 significant decimal digits.

print(3 + 2.0)          # 5.0 -- int promoted to float
print(int(3.99))         # 3   -- truncates toward zero, not rounds
print(int(-3.99))        # -3  -- also truncates toward zero
print(float(2 ** 60))    # a huge int converted to float can lose exactness beyond ~2**53

Key Takeaways

Python int has arbitrary precision — it grows as needed and never silently overflows.

float follows IEEE-754 double precision; many decimal fractions cannot be represented exactly, so never compare floats with == — use math.isclose() or the decimal module for exact needs.

complex numbers use a trailing j suffix and expose .real/.imag.

Literals can be written in decimal, hex (0x), octal (0o), binary (0b), and scientific notation (e/E); underscores can be inserted for readability without changing the value.

int() truncates toward zero rather than rounding; mixing int and float promotes the result to float.

if price_total == 19.90: print("exact match") import math if math.isclose(price_total, 19.90): print("close enough")

Binary floating-point cannot represent most decimal fractions exactly, so accumulated float arithmetic rarely equals a literal exactly. Use math.isclose() (or the decimal module for money) instead of ==.

rounded = int(2.5 + 0.5) # trying to 'round' by adding 0.5 then truncating -- wrong for negatives and not how round() works rounded = round(2.5) # use the built-in round() function int() always truncates toward zero; it is not a rounding function and breaks especially for negative numbers. Use the built-in round() for rounding to the nearest integer.

Why doesn't 0.1 + 0.2 equal 0.3 exactly in Python?

float uses IEEE-754 binary floating-point, and 0.1 and 0.2 cannot be represented exactly in binary, so their sum accumulates a tiny representation error.

How would you write the integer 26 in hexadecimal, octal, and binary literal form?

0x1A, 0o32, and 0b11010 respectively.

What does int(-7.8) evaluate to, and why?

-7 -- int() truncates toward zero rather than rounding, so it discards the fractional part regardless of sign.

Can a Python int ever overflow like a fixed-width integer in C?

No -- Python's int has arbitrary precision and grows automatically to represent arbitrarily large values, limited only by available memory.

What are .real and .imag on a complex number?

Attributes returning the real and imaginary components of the complex number as floats.

What is the purpose of underscores in a numeric literal like 1_000_000?

Purely readability -- they are ignored by the parser and do not change the numeric value.

Why should you use the decimal module instead of float for currency calculations?

Because float's binary representation introduces small rounding errors that accumulate and can produce incorrect totals in money math; decimal represents base-10 fractions exactly.

Write a function is_effectively_equal(a, b) that safely compares two floats using a tolerance.

import math def is_effectively_equal(a, b): return math.isclose(a, b, rel_tol=1e-9) print(is_effectively_equal(0.1 + 0.2, 0.3)) # True math.isclose() compares within a relative tolerance instead of exact equality, correctly handling floating-point rounding error.

What does this code print?

1000000 then 25 -- underscores are purely visual separators; 0b1111 is 15 and 0xA is 10, so 15 + 10 = 25.

Write code that computes 2**100 exactly and prints how many decimal digits the result has.

value = 2 100 print(value) print(len(str(value))) Python's arbitrary-precision int computes 2100 exactly with no overflow; converting to str and taking len() counts its decimal digits (31).

n = 1_000_000
print(n)
print(0b_1111 + 0x_A)