Numbers in Python: int, float, complex and bool

Integers are unbounded, floats are binary approximations, division always produces a float, and bool is quietly an integer. Each of those facts changes how you write arithmetic.

Integers have no ceiling

In most languages an integer occupies a fixed number of bits and overflows when it runs out. Python integers grow instead, limited only by available memory.

big = 2 ** 100
print(big)               # 1267650600228229401496703205376
print(big * big)         # exact, no overflow, no warning

This is convenient and it has a cost: arithmetic on very large integers is slower than on machine sized ones, because Python has to do the work in software. For ordinary values you will never notice.

Writing integer literals

decimal     = 1_000_000     # underscores group digits, value unchanged
hexadecimal = 0xFF          # 255
octal       = 0o17          # 15
binary      = 0b1011        # 11

print(bin(11), oct(15), hex(255))   # 0b1011 0o17 0xff

Floats are approximations

A float is a 64 bit binary approximation of a decimal number. Most decimal fractions have no exact binary form, so small errors are unavoidable.

print(0.1 + 0.2)                 # 0.30000000000000004
print(1.1 * 3)                   # 3.3000000000000003
print(0.1 + 0.2 == 0.3)          # False

Comparing floats properly

import math

print(math.isclose(0.1 + 0.2, 0.3))                    # True
print(abs((0.1 + 0.2) - 0.3) < 1e-9)                   # True, the manual form

Special float values

infinity = float("inf")
not_a_number = float("nan")

print(infinity > 10 ** 100)      # True
print(not_a_number == not_a_number)   # False. NaN equals nothing, including itself.
print(math.isnan(not_a_number))       # True, the correct test

The division operators

OperatorName7 and 2-7 and 2Result type
/true division3.5-3.5always float
//floor division3-4int if both are int
%modulo11int if both are int
**power49-49*depends on operands

* -7 ** 2 is -49 because ** binds tighter than the minus sign. (-7) ** 2 is 49.

print(7 / 2)         # 3.5   even though both operands are integers
print(7 // 2)        # 3
print(-7 // 2)       # -4    floor, not truncation towards zero
print(7 % 2)         # 1
print(-7 % 2)        # 1     the result carries the sign of the divisor
Floor division rounds down, towards negative infinity. Many languages truncate towards zero instead. This is the single most common source of off by one bugs when translating code into Python.

divmod

quotient, remainder = divmod(17, 5)
print(quotient, remainder)      # 3 2

# A practical use: seconds into minutes and seconds.
minutes, seconds = divmod(215, 60)
print(minutes, "min", seconds, "sec")    # 3 min 35 sec

bool is an integer

print(True == 1)                # True
print(False == 0)               # True
print(True + True)              # 2
print(isinstance(True, int))    # True

results = [92, 45, 78, 30, 66]
passed = sum(score >= 50 for score in results)
print(passed)                   # 3

The last example is a genuinely idiomatic use: each comparison produces True or False, and sum adds them as 1 and 0.

Rounding

print(round(3.7))          # 4
print(round(3.14159, 2))   # 3.14

print(round(0.5))          # 0
print(round(1.5))          # 2
print(round(2.5))          # 2

Python uses banker's rounding: an exact half rounds to the nearest even number. This keeps large sets of rounded values from drifting upwards. It surprises people the first time they meet it, and it is deliberate.

Useful built in numeric functions

print(abs(-9))              # 9
print(pow(2, 10))           # 1024
print(pow(2, 10, 1000))     # 24, that is (2 ** 10) % 1000 computed efficiently
print(min(4, 9, 2))         # 2
print(max([4, 9, 2]))       # 9
print(sum([1, 2, 3]))       # 6
print(int(9.99))            # 9, truncates towards zero
print(float(7))             # 7.0

Augmented assignment

total = 10
total += 5      # 15
total -= 3      # 12
total *= 2      # 24
total /= 4      # 6.0   note the type changed to float
total //= 2     # 3.0
total **= 2     # 9.0
total %= 4      # 1.0

Common mistakes

  • Expecting / to give an integer. It never does in Python 3. Use //.
  • Comparing floats with ==.
  • Assuming -7 // 2 is -3. It is -4.
  • Assuming round(2.5) is 3.
  • Using float for currency. Use decimal.Decimal, or store whole paise or cents as integers.
  • Writing -2 ** 2 and expecting 4.

Best practices

  • Use // whenever an index or a count is wanted, so the result stays an integer.
  • Use math.isclose for every float comparison.
  • Use underscores in long numeric literals; they cost nothing and prevent misreadings.
  • Keep money in integers of the smallest unit, or in Decimal. Never in float.

Practice

  1. Work out 17 // 5, -17 // 5, 17 % 5 and -17 % 5 on paper, then verify each one.
  2. Convert 4271 seconds into hours, minutes and seconds using divmod twice.
  3. Explain why round(0.5) and round(1.5) give different looking answers.
  4. Count how many values in a list are negative, using a single sum call and no if.
  5. Explain in two sentences why 0.1 + 0.2 != 0.3 without using the word "bug".

Conclusion

Integers are exact and unbounded, floats are fast and approximate, / always yields a float, // floors rather than truncates, and bool is an int in disguise. Those five facts cover almost every numeric surprise Python has to offer.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.