Python Math

The math of a computer program is most often nested in what is called an expression like this:

alt: this is an expression: 2 * 3 + 1

Mathematical expressions are built up with numbers and operators like + and *. When the line with the expression runs, Python evaluates it, running the math to figure out its value.

Evaluating an expression can be drawn as an arrow coming out of the expression, showing its computed value, like this:

alt: expression 2 * 3 + 1 evaluates to 7

Numbers - int and float

Surprisingly, there are two distinct types of numbers for doing arithmetic in a computer - int for whole integer numbers like 6 and 42 and -3, and float for numbers like 3.14 with a fractional part.

Add Subtract Multiply Divide + - * /

We'll start with basic int examples, and float works similarly. Addition, subtraction, and multiplication and division work the usual way with the operators: + - * /. For the most part these take in int values and produce int values. Division / is the exception, producing a float. Division by zero is an error.

>>> 2 + 1
3
>>> 1 + 10 - 2
9
>>> 2 * 3 * 4
24
>>> 2 * 6 / 3
4.0
>>> 6 / 0
ZeroDivisionError: division by zero

Precedence

In computer terminology, the "precedence" of multiplication and division is higher than that of addition and subtraction, so Python evaluates multiplication and division first in an expression. After accounting for precedence, the arithmetic is done left-to-right.

e.g. here the multiplication happens first, then the addition:

>>> 1 + 2 * 3
7
>>> 1 + 3 * 3 + 1
11

A "unary -" is a minus sign to the left of a value, indicating that it is negative. The precedence of unary minus is higher than the precedence of multiply and divide, and this probably matches your intuition about how these expressions work.

>>> x = 4
>>> 1 + -x * 2  # unary - before *
-7

Add Parenthesis

Add parenthesis in an expression to control which operations are evaluated first. It's fine to add parenthesis into your code to make explicit the operation order you want.

>>> (1 + 2) * 3   # Do the addition first
9

Left To Right: 60 / 2 * 3

What is the value of this expression: 60 / 2 * 3

The multiplication and division have the same precedence, so the evaluation proceeds left-to-right, applying each operator in turn to a running result. For 60 / 2 * 3, the steps are..

1. start with 60
2. 60 / 2 yielding 30.0
3. 30.0 * 3 yielding 90.0

The 2 is in the denominator, but the 3 is not. To put both the 2 and 3 in the denominator, use parenthesis e.g. 60 / (2 * 3)

>>> 60 / 2 * 3
90.0
>>> 60 / (2 * 3)
10.0

Exponentiation **

The ** operator does exponentiation, e.g. 3 ** 2 is 32

>>> 3 ** 2
9
>>> 2 ** 10
1024

The precedence of exponentiation is the highest, higher than the precedence of multiplication.

>>> 2 * 3 ** 2
18
>>> (2 * 3) ** 2
36

Note that the up-hat ^ operator is not exponentiation, a frequent misunderstanding (^ does bitwise exclusive-or, a rarely used operation).

Unlike most programming languages, Python int values do not have a maximum. Python allocates more and more bytes to store the int as it gets larger. The number of grains of sand making up the universe when I was in college was thought to be about 2100, playing the role of handy very-large-number (I think it's bigger now as they keep finding more universe). In Python, we can write an expression with a number that large, and it just works.

>>> 2 ** 100
1267650600228229401496703205376
>>> 2 ** 100 + 1
1267650600228229401496703205377

Division / Yields Float

Unlike the other operators, division / operator always produces a float, even if the division comes out even.

>>> 7 / 2   # always produces float, notice the "."
3.5
>>> 8 / 2
4.0

// int Division

Some algorithms make the most sense if all of the values are kept as ints, so we need a separate division operator that produces ints. Python's int-division operator // rounds down any fraction, always yielding an int result.

>>> 9 / 2    # "/" always yields a float
4.5
>>> 9 // 2   # "//" rounds down to int
4
>>> 8 // 2
4
>>> 87 // 8
10
>>> 80 // 8
10
>>> 79 // 8
9

Language aside: other languages like C and Java have a more complicated rules for determining when float vs. int division applies. Python simply has two different operators: / for float division and // for int division, and this seems easier to understand.

Int Modulo %

The "modulo" or "mod" operator % is essentially the remainder after int-division. So (23 % 10) yields 3 — divide 23 by 10 and 3 is the leftover remainder.

>>> 23 % 10
3
>>> 36 % 10
6
>>> 43 % 10
3
>>> 40 % 10  # mod result 0 means divided evenly
0
>>> 17 % 5
2
>>> 15 % 5
0

Two rules for mod:

1. The result of mod by n is always in the range 0..n-1 inclusive. So for example, mod by 10 will always yield a number in the range 0..9. (This echos the UBNI up-to-but-not-including convention from range(n) function.)

2. If the result of mod is 0, then that means the division came out evenly. For example 40 % 10 is 0, so we know that 10 divides 40 evenly.

The best practice is to only use mod with non-negative numbers. Modding by 0 is an error, just like dividing by 0.

>>> 43 % 0
ZeroDivisionError: integer division or modulo by zero

int() str() Conversion

This is an int number: 123

This is the string form of that number: '123'

The function int(s) converts a string to an int, and str(n) does the reverse. This follows the convention that Python provides a function with same name as a type, e.g. int(), tries to convert its input to that type.

>>> int('123')      # str -> int
123
>>> int('123') + 1  # arithmetic works with int
124
>>> int('xyz')      # error, as text is not an int
ValueError: invalid literal for int() with base 10: 'xyz'
>>>
>>> str(123)        # int -> str
'123'
>>> str(123) + '!'  # concat works with str
'123!'

abs() min() max() sum() Functions

There are a few mathematical related functions built in to Python, not depending on the math module.

>>> abs(-5)         # absolute value         
5
>>> min([5, 1, 3])
1
>>> max([5, 1, 3])
5
>>> sum([5, 1, 3])  # sum up numbers
13

Memory use approximation: int values of 256 or less are stored in a special way that uses very few bytes. Other ints take up about 24 bytes each in RAM.

Review Expressions

What is the value of each expression? Write the result as int (6) or float (6.0).

>>> 2 * 1 + 6
8
>>> 1 + 2 * 3 - 1
6
>>> 20 / 4 + 1
6.0
>>> 20 / (4 + 1)
4.0
>> 40 / 2 * 2
40.0
>>> 5 ** 2
25
>>> 7 / 2
3.5
>>> 7 // 2
3
>>> 13 % 10
3
>>> 20 % 10
0
>>> 42 % 20
2
>>> 31 % 20
11
>>> int('67')
67
>>> int('71' + '8')
718

Float Type

Floating point numbers are used to do math with real-number quantities, such as a velocity or angle. The regular math operators + - * / ** work the same as before, taking in and producing floats.

>>> 1.5 + 3.3
4.8
>>> 1.0 + 2.0 * 3.0   # precedence * before +
7.0

If an expression mixes an int value with a float value, like 26 + 2.0, the int value is converted to a float at that point, and the math proceeds with float values. This one-way street is called "promotion" from int to float.

>>> 1 + 2.0 + 3  # promotion
6.0
>>> 1 + 2 + 3
6
>>> 2.5 + 1
3.5

Float values can be written in scientific notation with the letter e or E, like this:

>>> 1.2e23 * 10
1.2e+24
>>> 1.0e-4
0.0001

Converting from float to int

The int() function drops the fractional part of a float number, essentially always rounding towards 0 (the same int() function does string to int conversion). The float(s) function attempts to read a float value out of a text string.

>>> int(4.3)       # convert float -> int by truncation 
4
>>> int(6.89)
6
>>> int(-5.4)
-5
>>>
>>> float('3.14')  # convert text string -> float
3.14

The round() function does traditional rounding of a float value to the nearest int value.

>>> round(4.3)
4
>>> round(6.89)
7
>>> round(-5.4)
-5
>>> round(4.0)
4

Optional detail about rounding: How should rounding work for a number like 2.5 which is exactly halfway between the ints 2 and 3? Most people know the simple round-up rule, rounding up to the next larger integer, e.g. 2.5 rounds up to 3. Python does something slightly different. Python uses the round-to-even convention which rounds to whichever int is even for the 0.5 case, e.g. 2.5 also rounds to 2, and 3.5 rounds to 4. This avoids certain statistical problems in groups of rounded numbers. This has little effect in practice, and is not something Python programmers need to memorize. Just mentioning it here so you are not confused when trying round() in the interpreter.

>>> round(2.5)  # chooses 2 instead of 3
2
>>> round(2.500001)
3
>>> round(3.5)  # chooses 4 instead of 3
4

The math Module of Functions

The module named math contains a very large number of mathematical functions, akin to the mathematical functions on a scientific calculator.

Here are some commonly used features within the math module, accessed with the syntax like math.sqrt(2). (full list of functions)

>>> import math
>>>
>>> math.sqrt(2)    # Square root
1.4142135623730951
>>> math.log(10)    # Log base e
2.302585092994046
>>> math.exp(1)     # e raised to power
2.718281828459045
>>>
>>> # Constants for pi and e:
>>> math.pi
3.141592653589793
>>> math.e
2.718281828459045

The math.sqrt() function will return an error for a negative number (see complex below)

There are trigonometric functions taking in radians.

>>> math.sin(math.pi / 2)
1.0
>>> math.tan(0.5)
0.5463024898437905
>>> math.cos(math.pi / 2)
6.123233995736766e-17

In the last example, the floating point system is producing a number extremely close to, but not quite zero, due to the inherent tiny error terms in floating point numbers (see float error).

Complex Numbers

Python has built in support for complex numbers. Write a complex number within parenthesis, using j to mark the imaginary part, like this: (2+1j). The complex number can be written without any spaces within the parenthesis. The regular operators + - * / etc. all work correctly with complex inputs. You can also write a complex number as complex(1, 2), but the j form is the default.

>>> (2+1j) * (6-2j)
(14+2j)
>>> (2+1j) - (6-2j)
(-4+3j)
>>> (2+1j) ** 2
(3+4j)
>>> complex(1, 2)
(1+2j)

Suppose you have a list of numbers, and your code adds up all the values in the list. This works fine with int values. It also will work if one of the numbers is complex. The + operator, will do the right thing at run time when it comes across the complex value. This is the sort of informal flexibility you get with Python, where it looks at the type of each value at run time.

The regular math module does not work with complex numbers, e.g. math.sqrt(-1) returns an error. This was thought to be the least confusing approach for the many Python users who do not want to introduce complex numbers. For those wanting functions with complex number support, use the cmath module and its included sqrt(), sin(), etc. functions instead of the regular math functions (cmath docs).

>>> import cmath
>>> cmath.sqrt(-1)
1j
>>> cmath.sin(1j)
1.1752011936438014j

Float Error Term

Famously, floating point numbers have a tiny error term that builds up way off 15 digits or so to the right. Mostly this error is not shown when the float value is printed, as a few digits are not printed. However, the error digits are real, throwing the float value off a tiny amount. The error term will appear sometimes, just as a quirk of how many digits are printed (see below). This error term is an intrinsic limitation of floating point values in the computer. (Perhaps this explains why CS people are drawn to do their algorithms with int if possible.)

>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1
0.30000000000000004  # this is why we can't have nice things
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7999999999999999

Mostly, an error 15 or so digits off to the right does not invalidate your computation. However, it means that code should not use == with float values, since the comparison will be thrown off by the error term. To compare 2 float values, the function math.isclose(a, b) will return True if they are very nearly same, allowing for a little error term difference.

>>> import math
>>> a = 5 * 3.14
>>> b = 6 * 3.14 - 3.14
>>>
>>> a == b   # == not reliable, yikes!
False
>>> 
>>> math.isclose(a, b)  # use isclose() instead
True
>>>

Alternately, to compare 2 float values you can subtract them and check that the absolute value of the difference is less than some small delta (this is what isclose() does internally).

Memory use approximation: float values take up about 24 bytes each.

History and Revisions

In Nov 2025, major revision adding table of contents, and math module and complex number coverage.

 

Copyright 2025 Nick Parlante