Arithmetic and Its Surprises
Python does arithmetic the way you learned it at school, with the symbols you would expect, until one day it does not. Division comes in two forms, remainders have their own symbol, and adding three coffees at 4.20 produces a number with a stray 1 fifteen places after the point. None of this is a bug. Each surprise has a reason, and knowing the reasons is what keeps money correct in your programs.
The Operations
Plus and minus are the usual signs. A star means multiply, and a forward slash means divide. Two stars mean "to the power of", so two stars ten is two to the tenth power, 1024. Brackets group things the way they do on paper: two plus three, in brackets, times four is twenty, while without brackets the multiplication happens first and the answer is fourteen.
>>> (2 + 3) * 4 20 >>> 2 + 3 * 4 14 >>> 2 ** 10 1024
Two Kinds of Division
A single slash always gives a float, even when the numbers divide evenly: seven divided by two is 3.5. A double slash is floor division: it gives the whole part and throws the rest away, so seven double-slash two is 3. Its partner is the percent sign, called modulo, which gives only the remainder: seven modulo two is 1.
>>> 7 / 2 3.5 >>> 7 // 2 3 >>> 7 % 2 1
The pair turns up constantly. Share 17 biscuits between 5 people: 17 double-slash 5 is 3 each, and 17 modulo 5 leaves 2 over. A number is even exactly when the remainder after dividing by two is zero. And "every third item" is a remainder question too.
Why 0.1 + 0.2 Is Not 0.3
Type zero point one plus zero point two at the prompt and Python answers 0.30000000000000004. The reason is the way decimals are stored. A computer stores a float in binary, and in binary one tenth cannot be written exactly, in the same way that one third cannot be written exactly in decimal. However many threes you write after 0.3, you never quite reach one third. The computer stores the closest binary value it can, and the tiny difference shows up when values are added.
This is the simplified picture; the full story belongs to a computer-science course. For everyday work it is enough to know three things. It is not a Python bug: nearly every programming language behaves this way. It is harmless for measurements, where the last fraction of a millionth does not matter. And it is wrong for money.
Money Needs Decimal
Here is the problem with money in real numbers. Three coffees at 4.20 each, added as floats, come to 12.600000000000001. A report would print 12.60 and look fine, but add enough of these and a total ends up a cent off from what the bank says. Python's decimal module counts in exact tenths and hundredths instead. The session below borrows its Decimal type with one line, from decimal import Decimal; Chapter 9 explains exactly how borrowing works. The same three coffees, as Decimal values, come to exactly 12.60.
>>> 4.20 + 4.20 + 4.20
12.600000000000001
>>> from decimal import Decimal
>>> Decimal("4.20") + Decimal("4.20") + Decimal("4.20")
Decimal('12.60')
Notice that each amount is written as text, in quotes, inside Decimal. That is deliberate. Written without quotes, 4.20 is first stored as a float, already slightly off, and Decimal faithfully keeps the error. The session below shows the long, exact value of the float 4.20. Creating a Decimal from text avoids that step completely, so from Chapter 4 on, every amount of money in this book is a Decimal made from text.
>>> Decimal(4.20)
Decimal('4.20000000000000017763568394002504646778106689453125')
Rounding for Display
When a number only needs to look tidy, round it at the moment you show it and keep the exact value underneath. The round function rounds to a number of places, but it drops trailing zeros, so 12.6 appears instead of 12.60. Formatting at the point of printing, which the next topic teaches, gives the familiar two decimal places. Store exact, show rounded.
>>> total = 12.600000000000001
>>> round(total, 2)
12.6
>>> f"{total:.2f}"
'12.60'
- "Python's arithmetic is broken." Every language that uses standard binary floats shows the same tiny errors. It is a property of the storage format, and it is why exact types like Decimal exist.
- "A single slash drops the fraction, like a calculator's whole-number mode." A single slash always gives a float, so seven divided by two is 3.5. It is the double slash that drops the fraction.
- "Rounding fixes money problems." Rounding hides a small error in one place while it keeps growing in others. Decimal avoids the error instead of hiding it.
- "
Decimal(4.20)andDecimal("4.20")are the same." Without quotes the number is a float first, error included. Create Decimal values from text.
- Money mistakes are the embarrassing kind. Knowing why floats drift means your first program that adds up money already adds it up correctly.
- Floor division and remainder are everyday tools for splitting, grouping, and "every nth item" logic.
Knowledge Check
What do 7 // 2 and 7 % 2 give?
- 3 and 1, the whole part and then the remainder left
- 3.5 and 0, a decimal answer with an empty remainder left
- 3 and 0.5, the whole part and the fraction
- 1 and 3, the remainder first and then the whole part
Why is 0.1 + 0.2 not exactly 0.3 in Python?
- Python rounds every decimal to one place internally
- A bug in Python that is fixed in newer versions
- One tenth cannot be stored exactly in binary
- The plus sign joins the two numbers as text
Why does this book use Decimal for money?
- Decimal values always print with a currency sign automatically
- Floats cannot store any amount larger than a thousand
- Decimal arithmetic runs much faster than float arithmetic
- It counts exact decimal places, so the cents never drift
Why is Decimal("4.20") written with quotes?
- Decimal refuses to accept numbers without quotes around them
- Without quotes, 4.20 becomes a slightly inexact float first
- The quotes make the number print with exactly two decimal places
- Quotes mark the value as money rather than a plain number
You got correct