Numbers in Python

Arbitrary precision, floating-point traps, division gotchas, and the surprising truth about booleans

1. Arbitrary Precision Integers

In most languages, integers have fixed sizes (32-bit, 64-bit) and overflow silently or throw errors. Python is different: integers have unlimited precision.

python
>>> 2 ** 100
1267650600228229401496703205376

>>> 2 ** 1000
# Returns a 302-digit number without breaking a sweat
Why This Matters
  • No overflow bugs — You'll never wrap around to a negative number unexpectedly
  • No int vs long distinction — Python 3 unified these; there's just int
  • Trade-off: Performance — Very large integers are slower because Python uses software-based arbitrary precision arithmetic instead of CPU-native operations
python
import math

# This is fine
factorial_100 = math.factorial(100)     # 158-digit number, instant

# This gets slow
factorial_100000 = math.factorial(100000) # Takes noticeable time

For cryptographic or scientific work with huge numbers, libraries like gmpy2 provide faster arbitrary-precision math.

2. Float Representation and Pitfalls

Python floats are IEEE 754 double-precision (64-bit), identical to double in C or Java. This isn't unique to Python, but the consequences surprise developers.

The Classic Trap

python
>>> 0.1 + 0.2
0.30000000000000004

>>> 0.1 + 0.2 == 0.3
False
Why This Happens

0.1 and 0.2 can't be represented exactly in binary floating point — they're approximations. This affects every language using IEEE 754, not just Python.

How to Handle This

Option 1: Tolerance-based comparison

python
>>> abs((0.1 + 0.2) - 0.3) < 1e-9
True

# Or use math.isclose (Python 3.5+)
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True

Option 2: Use Decimal for exact decimal arithmetic

python
from decimal import Decimal

# Always pass strings to Decimal, not floats
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True

# If you pass a float, you inherit its imprecision
>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

Option 3: Use Fraction for exact rational arithmetic

python
from fractions import Fraction

>>> Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10)
True

>>> Fraction('0.1') + Fraction('0.2')
Fraction(3, 10)

When to Use What

Use CaseType
General math, science, graphicsfloat
Money, financial calculationsDecimal
Exact ratios, symbolic mathFraction
Large integer math, cryptographyint

3. Division Behavior

Python 3 changed division semantics, which trips up developers coming from Python 2, C, or Java.

True Division vs Floor Division

python
>>> 5 / 2     # True division: always returns a float
2.5

>>> 5 // 2    # Floor division: returns an integer (if both operands are int)
2

>>> 5.0 // 2  # Floor division with float operand: returns a float
2.0

The Floor-Toward-Negative-Infinity Gotcha

This is where Python differs from C, Java, and many other languages:

Python — Floors Toward -∞
>>> -5 // 2
-3

# -5 / 2 = -2.5
# Floor DOWN to -3
C / Java — Truncates Toward 0
// -5 / 2 in C or Java:
-2

// -5 / 2 = -2.5
// Truncate TOWARD ZERO to -2
Why Python Does This

Floor division and modulo (%) are mathematically consistent in Python. The invariant (a // b) * b + (a % b) == a always holds. This makes modulo behave predictably with negative numbers — useful for things like circular array indexing.

Modulo With Negative Numbers

python
>>> -5 % 2
1      # Python: result has same sign as divisor

# In C: -5 % 2 might give -1 (result has same sign as dividend)

# Verify the invariant:
>>> a, b = -5, 2
>>> (a // b) * b + (a % b)
-5     # Always equals a

4. Booleans Are Integers

This is genuinely surprising: bool is a subclass of int.

python
>>> isinstance(True, int)
True

>>> True == 1
True

>>> False == 0
True

This Enables Useful Patterns

python
# Counting with sum:
>>> data = [1, -5, 3, -2, 8]
>>> sum(x > 0 for x in data)  # Count positive numbers
3

# Conditional arithmetic:
>>> discount_eligible = True
>>> price = 100 - (20 * discount_eligible)  # 80 if eligible, 100 if not
80

# Indexing with booleans:
>>> options = ["no", "yes"]
>>> options[True]
'yes'
>>> options[False]
'no'

The Gotcha

python
>>> True + True + True
3

>>> sum([True, True, False])
2

# True == 1 and False == 0, so they're duplicates in sets!
>>> {1, True, 0, False}
{0, 1}
Set and Dict Collisions

Because True == 1 and False == 0, they hash to the same values. In sets and dict keys, True and 1 are considered the same key, as are False and 0.

5. Numeric Literals and Readability

Underscores (Python 3.6+)

You can use underscores anywhere in numeric literals for readability. Python ignores them entirely — they're purely visual:

python
million     = 1_000_000
credit_card = 1234_5678_9012_3456
binary      = 0b_1111_0000_1010_0101
hex_color   = 0xFF_FF_FF

Different Bases

python
decimal     = 255
binary      = 0b11111111   # prefix: 0b
octal       = 0o377        # prefix: 0o
hexadecimal = 0xFF         # prefix: 0x

# All four are equal
>>> decimal == binary == octal == hexadecimal
True

Complex Numbers (Built-in!)

Python has native complex number support:

python
>>> z = 3 + 4j
>>> z.real
3.0
>>> z.imag
4.0
>>> abs(z)          # Magnitude
5.0
>>> z.conjugate()
(3-4j)

6. Common Mistakes and Edge Cases

Mistake 1: Division by Zero

python
>>> 1 / 0
# ZeroDivisionError

>>> 1.0 / 0.0
# ZeroDivisionError — Python doesn't return infinity like some languages

# But float infinity exists:
>>> float('inf')
inf
>>> float('inf') > 10**1000
True

Mistake 2: Assuming Integer Results

Wrong — Returns Float
def split_evenly(total, parts):
    return total / parts

>>> split_evenly(10, 2)
5.0  # float, not int
Right — Check and Convert
def split_evenly(total, parts):
    result = total / parts
    if result.is_integer():
        return int(result)
    return result

Mistake 3: Float Comparison in Loops

Dangerous — May Never Terminate
x = 0.0
while x != 1.0:
    x += 0.1
    print(x)
# After 10 iterations, x is
# 0.9999999999999999, not 1.0
Safe — Use < Instead
x = 0.0
while x < 1.0:
    x += 0.1
    print(x)

Mistake 4: Decimal from Float

Wrong — Inherits Imprecision
>>> Decimal(0.1)
Decimal('0.100000000000000
0055511151231257827021
181583404541015625')
Right — Use String
>>> Decimal('0.1')
Decimal('0.1')

7. Special Float Values

Python supports IEEE 754 special values:

python
>>> float('inf')    # Positive infinity
inf

>>> float('-inf')   # Negative infinity
-inf

>>> float('nan')    # Not a Number
nan
NaN Is Unlike Anything Else

NaN comparisons are always False, even with itself. This is the only value in Python where x == x is False.

python
>>> x = float('nan')
>>> x == x
False

# Use math.isnan() to check
>>> import math
>>> math.isnan(x)
True

Quick Reference: Python vs C/Java

FeaturePythonC / Java
Integer overflowNever (arbitrary precision)Wraps around or error
5 / 22.5 (float)2 (integer)
-5 // 2-3 (floor toward -∞)-2 (truncate toward 0)
-5 % 21 (sign of divisor)-1 (sign of dividend)
bool typeSubclass of intSeparate type
Complex numbersBuilt-in (3+4j)Requires library

Summary: Key Takeaways

ConceptKey Takeaway
Arbitrary precisionPython integers never overflow, but very large numbers are slower
Float comparisonNever use == with floats — use math.isclose() or tolerance
Decimal & FractionUse Decimal('0.1') (strings!) for money, Fraction for exact ratios
Division/ always returns float, // floors toward negative infinity
Booleansbool is a subclass of intTrue == 1, False == 0
ReadabilityUse underscores in literals: 1_000_000, 0xFF_FF
NaNNaN != NaN — always use math.isnan() to check