$$ \newcommand{\tp}{\thinspace .} $$

 

 

 

This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.

Another formula: Celsius-Fahrenheit conversion

Our next example involves the formula for converting temperature measured in Celsius degrees to the corresponding value in Fahrenheit degrees: $$ \begin{equation} F = \frac{9}{5}C + 32 \tag{3} \end{equation} $$ In this formula, \( C \) is the amount of degrees in Celsius, and \( F \) is the corresponding temperature measured in Fahrenheit. Our goal now is to write a computer program that can compute \( F \) from (3) when \( C \) is known.

Potential error: integer division

Straightforward coding of the formula

A straightforward attempt at coding the formula (3) goes as follows:

C = 21
F = (9/5)*C + 32
print F

The parentheses around 9/5 are not strictly needed, i.e., (9/5)*C is computationally identical to 9/5*C, but parentheses remove any doubt that 9/5*C could mean 9/(5*C). The section Arithmetic operators and precedence has more information on this topic.

When run under Python version 2.x, the program prints the value 53. You can find the program in the file c2f_v1.py in the src/formulas folder in the folder tree of example programs from this document (downloaded from http://hplgit.github.com/scipro-primer). The v1 part of the name stands for version 1. Throughout this document, we will often develop several trial versions of a program, but remove the version number in the final version of the program.

Verifying the results

Testing the correctness is easy in this case since we can evaluate the formula on a calculator: \( \frac{9}{5}\cdot 21+32 \) is 69.8, not 53. What is wrong? The formula in the program looks correct!

Float and integer division

The error in our program above is one of the most common errors in mathematical software and is not at all obvious for a newcomer to programming. In many computer languages, there are two types of divisions: float division and integer division. Float division is what you know from mathematics: 9/5 becomes 1.8 in decimal notation.

Integer division \( a/b \) with integers (whole numbers) \( a \) and \( b \) results in an integer that is truncated (or mathematically, rounded down). More precisely, the result is the largest integer \( c \) such that \( bc\leq a \). This implies that \( 9/5 \) becomes 1 since \( 1\cdot 5 = 5 \leq 9 \) while \( 2\cdot 5 = 10 >9 \). Another example is \( 1/5 \), which becomes 0 since \( 0\cdot 5 \leq 1 \) (and \( 1\cdot 5 > 1 \)). Yet another example is \( 16/6 \), which results in 2 (try \( 2\cdot 6 \) and \( 3\cdot 6 \) to convince yourself). Many computer languages, including Fortran, C, C++, Java, and Python version 2, interpret a division operation a/b as integer division if both operands a and b are integers. If either a or b is a real (floating-point) number, a/b implies the standard mathematical float division. Other languages, such as MATLAB and Python version 3, interprets a/b as float division even if both operands are integers, or complex division if one of the operands is a complex number.

The problem with our program is the coding of the formula (9/5)*C + 32. This formula is evaluated as follows. First, 9/5 is calculated. Since 9 and 5 are interpreted by Python as integers (whole numbers), 9/5 is a division between two integers, and Python version 2 chooses by default integer division, which results in 1. Then 1 is multiplied by C, which equals 21, resulting in 21. Finally, 21 and 32 are added with 53 as result.

We shall very soon present a correct version of the temperature conversion program, but first it may be advantageous to introduce a frequently used term in Python programming: object.

Objects in Python

When we write

C = 21

Python interprets the number 21 on the right-hand side of the assignment as an integer and creates an int (for integer) object holding the value 21. The variable C acts as a name for this int object. Similarly, if we write C = 21.0, Python recognizes 21.0 as a real number and therefore creates a float (for floating-point) object holding the value 21.0 and lets C be a name for this object. In fact, any assignment statement has the form of a variable name on the left-hand side and an object on the right-hand side. One may say that Python programming is about solving a problem by defining and changing objects.

At this stage, you do not need to know what an object really is, just think of an int object as a collection, say a storage box, with some information about an integer number. This information is stored somewhere in the computer's memory, and with the name C the program gets access to this information. The fundamental issue right now is that 21 and 21.0 are identical numbers in mathematics, while in a Python program 21 gives rise to an int object and 21.0 to a float object.

There are lots of different object types in Python, and you will later learn how to create your own customized objects. Some objects contain a lot of data, not just an integer or a real number. For example, when we write

print 'A text with an integer %d and a float %f' % (2, 2.0)

a str (string) object, without a name, is first made of the text between the quotes and then this str object is printed. We can alternatively do this in two steps:

s = 'A text with an integer %d and a float %f' % (2, 2.0)
print s

Avoiding integer division

As a quite general rule of thumb, one should be careful to avoid integer division when programming mathematical formulas. In the rare cases when a mathematical algorithm does make use of integer division, one should use a double forward slash, //, as division operator, because this is Python's way of explicitly indicating integer division.

Python version 3 has no problem with unintended integer division, so the problem only arises with Python version 2 (and many other common languages for scientific computing). There are several ways to avoid integer division with the plain / operator. The simplest remedy in Python version 2 is to write

from __future__ import division

This import statement must be present in the beginning of every file where the / operator always shall imply float division. Alternatively, one can run a Python program someprogram.py from the command line with the argument -Qnew to the Python interpreter:

Terminal> python -Qnew someprogram.py

A more widely applicable method, also in other programming languages than Python version 2, is to enforce one of the operands to be a float object. In the current example, there are several ways to do this:

F = (9.0/5)*C + 32
F = (9/5.0)*C + 32
F = float(C)*9/5 + 32

In the first two lines, one of the operands is written as a decimal number, implying a float object and hence float division. In the last line, float(C)*9 means float times int, which results in a float object, and float division is guaranteed.

A related construction,

F = float(C)*(9/5) + 32

does not work correctly, because 9/5 is evaluated by integer division, yielding 1, before being multiplied by a float representation of C (see next section for how compound arithmetic operations are calculated). In other words, the formula reads F=C+32, which is wrong.

We now understand why the first version of the program does not work and what the remedy is. A correct program is

C = 21
F = (9.0/5)*C + 32
print F

Instead of 9.0 we may just write 9. (the dot implies a float interpretation of the number). The program is available in the file c2f.py. Try to run it - and observe that the output becomes 69.8, which is correct.

Locating potential integer division

Running a Python program with the -Qwarnall argument, say

Terminal> python -Qwarnall someprogram.py

will print out a warning every time an integer division expression is encountered in Python version 2.

Remark

We could easily have run into problems in our very first programs if we instead of writing the formula \( \frac{1}{2}gt^2 \) as 0.5*g*t**2 wrote (1/2)*g*t**2. This term would then always be zero!

Arithmetic operators and precedence

Formulas in Python programs are usually evaluated in the same way as we would evaluate them mathematically. Python proceeds from left to right, term by term in an expression (terms are separated by plus or minus). In each term, power operations such as \( a^b \), coded as a**b, has precedence over multiplication and division. As in mathematics, we can use parentheses to dictate the way a formula is evaluated. Below are two illustrations of these principles.

As evident from these two examples, it is easy to unintentionally get integer division in formulas. Although integer division can be turned off in Python, we think it is important to be strongly aware of the integer division concept and to develop good programming habits to avoid it. The reason is that this concept appears in so many common computer languages that it is better to learn as early as possible how to deal with the problem rather than using a Python-specific feature to remove the problem.