Python — Numbers & Math
Python provides three built-in numeric types: int, float, and complex. Together with the math, decimal, fractions, and random modules, you have everything needed for numerical computing — from simple arithmetic to advanced mathematics.
Python integers are arbitrary-precision (unbounded). They never overflow — they grow as large as memory allows. This is a major difference from languages like C or Java where integers wrap at a fixed size.
| 1 | # Unbounded precision — no overflow |
| 2 | tiny = 1 |
| 3 | huge = 10 ** 100 # 1 followed by 100 zeros |
| 4 | print(huge) |
| 5 | # → 10000000000000000000000000000000000000000000000000000... |
| 6 | |
| 7 | huger = 2 ** 10000 # still works fine |
| 8 | print(len(str(huger))) # → 3011 digits |
| 9 | |
| 10 | # Integer literal bases |
| 11 | dec = 42 # decimal |
| 12 | octal = 0o52 # octal → 42 |
| 13 | hexa = 0x2A # hex → 42 |
| 14 | binary = 0b101010 # binary → 42 |
| 15 | |
| 16 | # Underscores for readability (Python 3.6+) |
| 17 | big = 1_000_000_000 # same as 1000000000 |
| 18 | hex_mask = 0xFF_FF_FF_FF |
| 19 | |
| 20 | # Integer methods |
| 21 | n = 42 |
| 22 | print(n.bit_length()) # → 6 (bits needed: 101010) |
| 23 | print(n.to_bytes(2, "big")) # → b'\x00*' |
| 24 | print(n.to_bytes(2, "little")) # → b'*\x00' |
| 25 | |
| 26 | # from_bytes — reconstruct integer |
| 27 | raw = b'\x00*' |
| 28 | print(int.from_bytes(raw, "big")) # → 42 |
| 29 | print(int.from_bytes(raw, "little")) # → 13312 |
| 30 | |
| 31 | # Type conversion |
| 32 | print(int(3.9)) # → 3 (truncates toward zero) |
| 33 | print(int("42")) # → 42 (string to int) |
| 34 | print(int("FF", 16)) # → 255 (parse hex string) |
| 35 | print(True + 2) # → 3 (bool is int subclass) |
info
Floats are IEEE 754 double-precision (64-bit) binary floating-point numbers. They have ~15-17 decimal digits of precision and a range of roughly 5e-324 to 1.8e+308.
| 1 | # Float precision — the classic gotcha |
| 2 | print(0.1 + 0.2) # → 0.30000000000000004 |
| 3 | print(0.1 + 0.2 == 0.3) # → False (!) |
| 4 | |
| 5 | # Why? 0.1 and 0.2 have no exact binary representation |
| 6 | # in IEEE 754, just like 1/3 has no exact decimal form |
| 7 | |
| 8 | # Workaround: use math.isclose for comparison |
| 9 | import math |
| 10 | print(math.isclose(0.1 + 0.2, 0.3)) # → True |
| 11 | |
| 12 | # Or round to needed precision |
| 13 | print(round(0.1 + 0.2, 10) == 0.3) # → True |
| 14 | |
| 15 | # Special float values |
| 16 | inf = float("inf") |
| 17 | neg_inf = float("-inf") |
| 18 | nan = float("nan") |
| 19 | |
| 20 | print(inf > 1e308) # → True |
| 21 | print(inf + 1) # → inf |
| 22 | print(-inf + 1) # → -inf |
| 23 | print(inf - inf) # → nan |
| 24 | print(nan == nan) # → False (!) — NaN never equals anything |
| 25 | print(math.isnan(nan)) # → True — use isnan to check |
| 26 | |
| 27 | # Float limits |
| 28 | import sys |
| 29 | print(sys.float_info.max) # → 1.7976931348623157e+308 |
| 30 | print(sys.float_info.min) # → 2.2250738585072014e-308 |
| 31 | print(sys.float_info.epsilon) # → 2.220446049250313e-16 |
| 32 | |
| 33 | # Type conversion |
| 34 | print(float(42)) # → 42.0 |
| 35 | print(float("3.14")) # → 3.14 |
| 36 | print(float("inf")) # → inf |
warning
Complex numbers have a real and imaginary part, written as a + bj. The imaginary unit is j (not i as in mathematics).
| 1 | # Creation |
| 2 | z1 = 3 + 4j # literal form |
| 3 | z2 = complex(3, 4) # constructor |
| 4 | print(z1) # → (3+4j) |
| 5 | |
| 6 | # Real and imaginary parts |
| 7 | print(z1.real) # → 3.0 |
| 8 | print(z1.imag) # → 4.0 |
| 9 | |
| 10 | # Conjugate |
| 11 | print(z1.conjugate()) # → (3-4j) |
| 12 | |
| 13 | # Arithmetic works as expected |
| 14 | a = 1 + 2j |
| 15 | b = 3 + 4j |
| 16 | print(a + b) # → (4+6j) |
| 17 | print(a * b) # → (-5+10j) (1+2j)*(3+4j) = 3+4j+6j+8j² = -5+10j |
| 18 | print(a / b) # → (0.44+0.08j) |
| 19 | |
| 20 | # Magnitude and phase |
| 21 | import cmath |
| 22 | print(abs(a)) # → 2.236... (magnitude: sqrt(1²+2²)) |
| 23 | print(cmath.phase(a)) # → 1.107... (radians) |
| 24 | print(cmath.polar(a)) # → (2.236, 1.107) (r, theta) |
| 25 | print(cmath.rect(2.236, 1.107)) # → (1+2j) |
| 26 | |
| 27 | # Type conversion — only from other numeric types |
| 28 | print(complex(5)) # → (5+0j) |
| 29 | print(complex(5.0)) # → (5+0j) |
Python provides a full set of arithmetic and comparison operators. Note that / always returns a float, while // performs floor division.
| 1 | # Arithmetic operators |
| 2 | print(10 + 3) # → 13 addition |
| 3 | print(10 - 3) # → 7 subtraction |
| 4 | print(10 * 3) # → 30 multiplication |
| 5 | print(10 / 3) # → 3.3333333333333335 true division (always float) |
| 6 | print(10 // 3) # → 3 floor division (rounds down) |
| 7 | print(10 % 3) # → 1 modulo (remainder) |
| 8 | print(10 ** 3) # → 1000 exponentiation (pow) |
| 9 | |
| 10 | # Floor division behavior with negatives |
| 11 | print(-10 // 3) # → -4 (rounds DOWN, not toward zero) |
| 12 | print(10 // -3) # → -4 |
| 13 | print(-10 // -3) # → 3 |
| 14 | |
| 15 | # Modulo with negatives — result has sign of divisor |
| 16 | print(-10 % 3) # → 2 (because -10 // 3 = -4, -4*3 + 2 = -10) |
| 17 | print(10 % -3) # → -2 |
| 18 | |
| 19 | # Exponentiation |
| 20 | print(2 ** 3) # → 8 |
| 21 | print(2 ** 0.5) # → 1.414... (sqrt via exponent) |
| 22 | print(2 ** -1) # → 0.5 |
| 23 | print(2 ** 1000) # huge int — no overflow |
| 24 | |
| 25 | # Augmented assignment |
| 26 | x = 10 |
| 27 | x += 3 # x = x + 3 → 13 |
| 28 | x -= 5 # x = x - 5 → 8 |
| 29 | x *= 2 # x = x * 2 → 16 |
| 30 | x /= 4 # x = x / 4 → 4.0 (becomes float) |
| 31 | x //= 2 # x = x // 2 → 2.0 (still float) |
| 32 | x = 10 |
| 33 | x %= 3 # x = x % 3 → 1 |
| 34 | x **= 3 # x = x ** 3 → 1 |
| 35 | |
| 36 | # Comparison operators |
| 37 | print(5 == 5) # → True (equal) |
| 38 | print(5 != 5) # → False (not equal) |
| 39 | print(5 < 10) # → True |
| 40 | print(5 > 10) # → False |
| 41 | print(5 <= 5) # → True |
| 42 | print(5 >= 5) # → True |
| 43 | |
| 44 | # Chained comparisons — a Python superpower |
| 45 | age = 25 |
| 46 | print(18 <= age < 65) # → True (equivalent to: 18 <= age and age < 65) |
| 47 | print(0 < age < 100) # → True |
| 48 | |
| 49 | # Comparison across numeric types |
| 50 | print(1 == 1.0) # → True (value comparison, not type) |
| 51 | print(1 == 1 + 0j) # → True |
| 52 | print(42 < 42.5) # → True (int vs float comparison works) |
| 53 | |
| 54 | # Note: complex numbers don't support <, >, <=, >= |
| 55 | # print(3+4j < 5+6j) # TypeError: '>' not supported |
info
The math module provides access to common mathematical functions and constants built on top of the C standard library.
| 1 | import math |
| 2 | |
| 3 | # Rounding functions |
| 4 | print(math.ceil(3.1)) # → 4 (round up) |
| 5 | print(math.ceil(-3.1)) # → -3 (round up toward infinity) |
| 6 | print(math.floor(3.9)) # → 3 (round down) |
| 7 | print(math.floor(-3.9)) # → -4 (round down toward -infinity) |
| 8 | print(math.trunc(3.9)) # → 3 (truncate toward zero) |
| 9 | print(math.trunc(-3.9)) # → -3 (truncate toward zero) |
| 10 | print(round(3.5)) # → 4 (built-in, banker's rounding) |
| 11 | print(round(4.5)) # → 4 (rounds to even — banker's rounding!) |
| 12 | print(round(3.14159, 2)) # → 3.14 (round to 2 decimal places) |
| 13 | |
| 14 | # Power and roots |
| 15 | print(math.sqrt(16)) # → 4.0 |
| 16 | print(math.pow(2, 10)) # → 1024.0 (always returns float) |
| 17 | print(2 ** 10) # → 1024 (returns int when possible) |
| 18 | |
| 19 | # Logarithms |
| 20 | print(math.log(100)) # → 4.605... (natural log, base e) |
| 21 | print(math.log(100, 10)) # → 2.0 (log base 10) |
| 22 | print(math.log10(100)) # → 2.0 (log base 10, faster) |
| 23 | print(math.log2(1024)) # → 10.0 (log base 2, faster) |
| 24 | |
| 25 | # Trigonometry (angles in radians) |
| 26 | print(math.sin(math.pi / 2)) # → 1.0 |
| 27 | print(math.cos(0)) # → 1.0 |
| 28 | print(math.tan(math.pi / 4)) # → 0.999... |
| 29 | print(math.asin(1)) # → pi/2 |
| 30 | print(math.degrees(math.pi)) # → 180.0 |
| 31 | print(math.radians(90)) # → pi/2 |
| 32 | |
| 33 | # Constants |
| 34 | print(math.pi) # → 3.141592653589793 |
| 35 | print(math.e) # → 2.718281828459045 |
| 36 | print(math.tau) # → 6.283185307179586 (2 * pi) |
| 37 | |
| 38 | # Special values |
| 39 | print(math.inf) # → inf |
| 40 | print(math.nan) # → nan |
| 41 | print(math.isinf(1/0)) # → True |
| 42 | print(math.isnan(float("nan"))) # → True |
| 43 | |
| 44 | # Other useful functions |
| 45 | print(math.factorial(5)) # → 120 |
| 46 | print(math.comb(5, 2)) # → 10 (combinations: 5 choose 2) |
| 47 | print(math.perm(5, 2)) # → 20 (permutations: 5 pick 2) |
| 48 | print(math.gcd(48, 18)) # → 6 (greatest common divisor) |
| 49 | print(math.lcm(12, 18)) # → 36 (least common multiple, 3.9+) |
| 50 | print(math.fabs(-3.5)) # → 3.5 (absolute value as float) |
| 51 | print(math.copysign(5, -3)) # → -5.0 (magnitude of 5, sign of -3) |
best practice
For exact decimal arithmetic (e.g., financial calculations), use the decimal module. For rational number arithmetic, use fractions.
| 1 | from decimal import Decimal, getcontext, ROUND_HALF_UP |
| 2 | |
| 3 | # Decimal from string (never from float!) |
| 4 | price = Decimal("0.10") |
| 5 | tax = Decimal("0.20") |
| 6 | print(price + tax) # → 0.30 (exact!) |
| 7 | print(0.10 + 0.20) # → 0.30000000000000004 (float — wrong) |
| 8 | |
| 9 | # Setting precision and rounding |
| 10 | getcontext().prec = 28 # default precision (28 decimal places) |
| 11 | getcontext().rounding = ROUND_HALF_UP # standard rounding |
| 12 | |
| 13 | # Decimal arithmetic |
| 14 | d = Decimal("10.50") |
| 15 | print(d / 3) # → 3.5 (exact with context precision) |
| 16 | print(d.quantize(Decimal("0.1"))) # → 10.5 (round to 1 decimal) |
| 17 | |
| 18 | # Comparing decimals |
| 19 | print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3")) # → True |
| 20 | |
| 21 | # Fractions — exact rational arithmetic |
| 22 | from fractions import Fraction |
| 23 | f1 = Fraction(1, 3) # 1/3 |
| 24 | f2 = Fraction(2, 5) # 2/5 |
| 25 | |
| 26 | print(f1 + f2) # → 11/15 |
| 27 | print(f1 * f2) # → 2/15 |
| 28 | print(f1 / f2) # → 5/6 |
| 29 | print(float(f1)) # → 0.333... |
| 30 | print(f1.limit_denominator(10)) # → 1/3 (already simplified) |
warning
The random module implements pseudo-random number generators for various distributions and sampling operations.
| 1 | import random |
| 2 | |
| 3 | # Basic random floats |
| 4 | print(random.random()) # → 0.0 <= x < 1.0 (uniform) |
| 5 | print(random.uniform(5, 10)) # → 5.0 <= x <= 10.0 (uniform float range) |
| 6 | |
| 7 | # Integers |
| 8 | print(random.randint(1, 6)) # → int: 1 <= x <= 6 (inclusive both ends) |
| 9 | print(random.randrange(10)) # → 0..9 |
| 10 | print(random.randrange(0, 10, 2)) # → 0, 2, 4, 6, 8 (step) |
| 11 | |
| 12 | # Sequence operations |
| 13 | colors = ["red", "green", "blue", "yellow"] |
| 14 | print(random.choice(colors)) # → single random element |
| 15 | |
| 16 | # Shuffle in-place |
| 17 | random.shuffle(colors) # → colors list is now permuted |
| 18 | |
| 19 | # Sample without replacement |
| 20 | cards = list(range(52)) |
| 21 | hand = random.sample(cards, 5) # → 5 unique cards (no duplicates) |
| 22 | |
| 23 | # Sample with replacement (allow duplicates) |
| 24 | hands = random.choices(cards, k=5) # → 5 cards, possible duplicates |
| 25 | |
| 26 | # Weighted choices |
| 27 | random.choices( |
| 28 | ["red", "green", "blue"], |
| 29 | weights=[5, 3, 1], # red is 5x more likely than blue |
| 30 | k=10 |
| 31 | ) |
| 32 | |
| 33 | # Seeding for reproducibility |
| 34 | random.seed(42) |
| 35 | print(random.random()) # always 0.639... |
| 36 | random.seed(42) |
| 37 | print(random.random()) # same value — deterministic sequence |
info
Bitwise operators work on the binary representation of integers. They are useful for flags, masks, and low-level protocols.
| 1 | # Bitwise operators — operate on binary bits |
| 2 | a = 0b1100 # 12 in decimal |
| 3 | b = 0b1010 # 10 in decimal |
| 4 | |
| 5 | print(bin(a)) # → 0b1100 (binary representation) |
| 6 | |
| 7 | print(a & b) # AND → 0b1000 (8) |
| 8 | print(a | b) # OR → 0b1110 (14) |
| 9 | print(a ^ b) # XOR → 0b0110 (6) |
| 10 | print(~a) # NOT → -13 (inverts all bits, two's complement) |
| 11 | print(a << 2) # LSHIFT → 0b110000 (48) — multiply by 2² |
| 12 | print(a >> 2) # RSHIFT → 0b0011 (3) — floor divide by 2² |
| 13 | |
| 14 | # Common use: flags and masks |
| 15 | READ = 0b001 # 1 |
| 16 | WRITE = 0b010 # 2 |
| 17 | EXEC = 0b100 # 4 |
| 18 | |
| 19 | permissions = READ | WRITE # → 3 (0b011) |
| 20 | print(permissions & READ) # → 1 (True — has read) |
| 21 | print(permissions & EXEC) # → 0 (False — no execute) |
| 22 | print(permissions ^ READ) # → 2 (toggle read bit) |
| 23 | |
| 24 | # Bit_length — useful for binary operations |
| 25 | n = 255 |
| 26 | print(n.bit_length()) # → 8 (bits: 11111111) |
| 27 | print(n.bit_count()) # → 8 (popcount, 3.8+) |
| 28 | |
| 29 | # Check if power of two |
| 30 | def is_power_of_two(x): |
| 31 | return x > 0 and (x & (x - 1)) == 0 |
| 32 | |
| 33 | print(is_power_of_two(8)) # → True |
| 34 | print(is_power_of_two(10)) # → False |
info
| 1 | # 1. Use int for counting, indexing, whole numbers |
| 2 | count = 1000 # int — fine |
| 3 | |
| 4 | # 2. Use Decimal for money |
| 5 | from decimal import Decimal |
| 6 | price = Decimal("19.99") |
| 7 | tax = price * Decimal("0.08") |
| 8 | total = price + tax # exact decimal result |
| 9 | |
| 10 | # 3. Avoid float equality — use tolerance |
| 11 | import math |
| 12 | if math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9): |
| 13 | print("close enough") |
| 14 | |
| 15 | # 4. Use Fraction for exact rationals |
| 16 | from fractions import Fraction |
| 17 | ratio = Fraction(22, 7) # exact 22/7 |
| 18 | |
| 19 | # 5. Use ** or pow for integer exponentiation |
| 20 | # (math.pow always returns float, losing precision for large ints) |
| 21 | print(2 ** 100) # int — correct |
| 22 | print(math.pow(2, 100)) # float — rounded |
| 23 | |
| 24 | # 6. Integer division gotcha |
| 25 | print(5 / 2) # → 2.5 (float — may surprise beginners) |
| 26 | print(5 // 2) # → 2 (int — floor) |
| 27 | |
| 28 | # 7. Large ints are slower |
| 29 | import time |
| 30 | n = 10 ** 10000 |
| 31 | # operations on this are O(n) in digit count |
| 32 | |
| 33 | # 8. Type conversion awareness |
| 34 | print(int(3.999)) # → 3 (truncates, not rounds!) |
| 35 | print(round(3.999)) # → 4 (rounds) |
| 36 | print(int("0xFF", 16)) # → 255 (parse hex string) |
| 37 | |
| 38 | # 9. NaN comparisons |
| 39 | nan = float("nan") |
| 40 | print(nan == nan) # → False (always!) |
| 41 | print(nan is nan) # → True (identity works) |
| 42 | |
| 43 | # 10. bool is a subclass of int |
| 44 | print(True + True) # → 2 |
| 45 | print(False * 10) # → 0 |
| 46 | print(isinstance(True, int)) # → True — but don't abuse this |