Lesson 1. Arithmetic

2023-01-23

Using Python as a Scientific Calculator

We can use Python as a scientific calculator to perform simple arithmetic.
Although this kind of interactive use employs only a small fraction of Python’s capabilities, it is the natural place to start before tackling more advanced topics on vectors, functions and plotting in later modules.

Objectives

In this lesson, you will learn how to evaluate arithmetic expressions in Python and understand the results produced, particularly when scientific notation, complex numbers, and infinite or undefined answers are involved.
After completing the lesson you should know

Although Python’s arithmetic is reasonably intuitive, certain intrinsic limitations of digital computers lead to some subtleties that can be confusing when you first meet them.


Basic Operations and Order of Precedence

The Python symbol for each of the five main arithmetic operators is shown in the following table. The first four are obvious.

Operator Symbol
Addition +
Subtraction -
Multiplication *
Division /
Exponentiation **

In an arithmetic expression, the order of precedence is as follows.

  1. Parentheses
  2. Exponentiation
  3. Multiplication and Division
  4. Addition and Subtraction

Operations with the same precedence are evaluated from left to right.

Examples

Try out the following examples. In each case, first apply the precedence rules to calculate the answer yourself, and then type the expression in the IPython pane of Spyder to confirm Python’s answer.

1 + 2*3
(1+2)*3

4/2 + 1
1 + 4/2
4/(2+1)

8**2 / 3
8**(2/3)

12/2/3
12/(2*3)

4**3**2
4**(3**2)
(4**3)**2

2*-3
2**-3

In the last two expressions, the minus sign is interpreted as a unary rather than a binary operator. The unary minus has a higher precedence than * and ** so the results are 2*(-3) and 2**(-3).

In practice, if you are unsure about how Python will evaluate an expression, it is best to use explicit parentheses. Doing so will make your code easier to read.

Exercise Type a command to evaluate the fraction with numerator 17.1+20.3 and denominator 36.5+41.8. Hint: the answer is not 17.1 + 20.3/36.5 + 41.8.

Integers versus Floating-Point Numbers

The type function tells you the type of any data object in Python. For example, the commands

type(3)
type(3.0)

show that 3 is an int whereas 3.0 is a float. Not surprisingly, int is short for ‘integer’ (i.e., a whole number), but what about float?
For any real number, we can ‘float’ the decimal point \(n\) places to the left or right by incorporating a factor of \(10^n\) or \(10^{-n}\). For example, \[ 2\,347.10293=2.347\,102\,93\times10^3=234\,710\,293\times10^{-5}. \] Any positive real number has a unique representation as a normalised floating-point number, that is, as a product \(m\times10^e\) where \(1\le m<10\) and \(e\) is an integer. The number \(m\) is called the mantissa (or significand) and \(e\) is called the exponent.

For human convenience, Python displays floating-point numbers in base 10, but the computer hardware works in binary, so the real floating-point representation is really \(m\times2^e\) with \(1\le m<2\). Moreover, for reasons of efficiency, all modern computer CPUs are designed to work with a standard 64-bit floating-point format. This fixed size imposes fundamental limits on the magnitude of a floating-point number and on the number of significant figures that can be retained.

The NumPy function finfo provides information about the machine limits of floating-point data types. By typing the command

print(finfo(float))

you can see that the binary exponent ranges from \(-1022\) to \(+1024\), and that the largest floating-point number is about \(1.8\times10^{308}\). The smallest positive normalised floating-point number is about \(2.2\times10^{-308}\) but if we allow a mantissa smaller than \(1\) then we can get as low as \(4.9\times10^{-324}\).

The most important parameter of a floating-point number system is the machine epsilon, defined as the smallest floating-point number \(\epsilon\) such that the computed values of \(1+\epsilon\) is strictly greater than \(1\). Thus, if \(x\) is any positive machine number strictly less than \(\epsilon\), then \(1+x\) evaluates to \(1\). For standard 64-bit floating-point numbers, the machine epsilon is \(2^{-52}\approx2.22\times10^{-16}\). The practical consequence is that in any floating-point calculation we cannot expect accuracy better than about 15 correct significant figures.

We can use exponential format, also called scientific format, when entering or displaying floating-point numbers. For example, Python will display \(2.3\times10^{17}\) as 2.3e17, and display \(7.3\times10^{-8}\) as 7.3e-08. This format is the most practical if you have to deal with very large or very small numbers.

Mixed-Mode Arithmetic

The binary representation of an integer is quite different from the binary representation of the equivalent float so to the computer 3 looks quite different from 3.0. Nevertheless, if you combine integer and floating-point numbers in arithmetic expressions, then Python will promote each int to its corresponding float to obtain a purely floating-point expression that is then evaluated. For example, 3 + 1/2 becomes 3.0 + 0.5 and then 3.5.

Notice that division is unusual as a binary operation, because the result a/b can have a different type from the operands a and b. If a and b are of type int, then a/b is of type float, even if a/b is a whole number; try the following commands.

8/4
8.0/4.0

In fact, Python provides a second kind of division operation: // carries out integer division. Closely related is the % operator that calculates the remainder. Try evaluating

8//4
8%4
7//4
7%4

Exercise Look up the docstring for the function divmod and use it to find the quotient and remainder when we divide \(3204\) by \(129\).

Complex Numbers

In mathematics, a complex number consists of a real part and an imaginary part, both of which are real numbers. Similarly, the complex data type in Python consists of two float numbers. Consider the complex number \(z=3+5i\). Python provides two ways to define this number: z = complex(3,5) and z = 3 + 5j. Thus, Python follows the engineering notation j for the imaginary unit. Note, however, that you cannot use j by itself, you have to type 1j. Try the command

1j * 1j

If z is a complex number, then z.real and z.imag give the real and imaginary parts. The abs function is used to find the modulus of a complex number. For example, try

z = 4 - 3j
abs(z)

The angle function gives the phase (polar angle) of a complex number. Try

z = complex(1, sqrt(3))

You can even get the angle in degrees instead of radians by doing

angle(z, deg=True)

Python versus NumPy Arithmetic

We mentioned in Lesson 0 that the NumPy module provides a fast array data type that is essential for many scientific and technical applications. NumPy is an example of an extension module, meaning a Python module that is not actually written in Python but instead uses the C language. In this way, many computationally intensive calculations can be performed 10 to 100 times faster than would be possible running a pure Python program with the standard Python interpreter.

However, a complication is that there are some differences between the way arithmetic works in C from how it works in Python. Type the NumPy-free commands

1/0
1.0/0.0

Both give a ‘ZeroDivisionError’, which is the standard Python behaviour. NumPy defines a float64 data type that behaves like a 64-bit floating point number in C. Type the commands

npzero = float64(0.0)
1/npzero
1.0/npzero

We see that division by npzero produces a RuntimeWarning about division by zero, but does not raise an error. Instead, the expression evaluates to inf, which represents ‘infinity’. Some expressions involving inf evaluate to a finite result. Try the following.

exp(-inf)
arctan(inf)
tanh(-inf)
cos(1/inf)

There are however some arithmetic expressions that cannot be given any meaningful value, either finite or infinite. Try

npzero / npzero
inf - 2*inf
sin(inf)
log(-1.0)

In each case, the result is nan, which stands for ‘not-a-number’. There is nothing magic about inf and nan; both are just particular sequences of 64 bits that are distinct from all of the sequences used to represent ordinary finite floating-point numbers.

Python versus NumPy Mathematical Functions

The Python math module provides several functions with the same names as NumPy functions. The examples above are all NumPy functions assuming you have set up Spyder to load NumPy in the IPython pane. Try the following.

import math
math.log(-1.0)
math.sin(inf)

In both cases, Python raises a ValueError: math domain error, instead of returning nan.


Summary

In this lesson, you have learned about

Further Reading

A classic paper is David Golberg, What every computer scientist should know about floating-point arithmetic, ACM Computing Surveys, Volume 23, pp 5-48, 1991.

Back to All Lessons