Legends ofPythos
Claim your name
The Standard Library

Floating point and exact numbers

Lesson 4 of 7

Watch the lesson1:25 · with Torsten
You have probably seen this in Python: 0.1 + 0.2 == 0.3 returns False. That looks like a bug, but it is not one.

Why floats are approximate

A float stores a number in binary, the way your computer does. In binary, 0.1 cannot be written exactly any more than you can write 1/3 as a decimal without repeating digits (0.333...). A float keeps 53 binary digits, about 16 decimal digits, so it saves a value very close to 0.1 but not equal to it.
print(repr(0.1))
print(repr(0.2))
print(repr(0.1 + 0.2))
Look at the full representation.
import math
print(0.1 + 0.2 == 0.3)               # False
print(math.isclose(0.1 + 0.2, 0.3))   # True
Compare floats with a tolerance

round() has its own quirks

print(round(2.5))   # 2, not 3
print(round(3.5))   # 4
Banker's rounding: halves go to the nearest even digit.
round() rounds halves to the nearest even number, a rule called banker's rounding. That is why round(2.5) gives 2 and round(3.5) gives 4.

decimal.Decimal: exact base-10 numbers

from decimal import Decimal
print(Decimal('0.1') + Decimal('2'))
# 2.1 exactly, no binary noise
Decimal stores the digits you give it.
decimal.Decimal keeps the base-10 digits you type, so it is ideal for money and anything that must be exact in base 10.
from decimal import Decimal
price = Decimal('2.675')
print(price.quantize(Decimal('0.01')))   # 2.68, exactly as written
print(round(2.675, 2))                   # 2.67, the float surprise
Rounding money to pennies
.quantize(Decimal('0.01')) rounds to two decimal places. Because the Decimal holds exactly 2.675, a true half, it rounds to 2.68; the float was a little under the half, so it could not. Like round(), quantize sends an exact half to the even digit, so Decimal('2.665') gives 2.66.

fractions.Fraction: exact ratios

from fractions import Fraction
x = Fraction(1, 3)
print(x * 3)   # 1 exactly
print(Fraction(2.5))
Fractions stay exact through arithmetic.
Fraction stores a numerator and denominator as integers, so ratios like 1/3 never lose precision.

Your turn

0 of 3 solved

Exercise 1

+40 XP
Write same(a, b), which says whether two floats are close enough to count as equal, using math.isclose. Then store [round(0.5), round(1.5), round(2.5), round(3.5)] in halves and print it, to see how round treats halves.
import math




def same(a, b):
    pass




halves = []

Run your code to check it against the tests.

Exercise 2

+40 XP
Money must be exact. Write total(prices), where prices is a list of strings such as '0.10': add them up as Decimal values and return the sum rounded to pennies with quantize(Decimal('0.01')). An empty list gives Decimal('0.00'). Then write split_bill(amount, people), which takes the amount as a string and returns each person's share as a Decimal rounded to pennies. For example, split_bill('10.00', 3) is Decimal('3.33').
from decimal import Decimal




def total(prices):
    pass




def split_bill(amount, people):
    pass

Run your code to check it against the tests.

Exercise 3

+40 XP
Write mix(parts), where parts is a list of fraction strings such as '1/3': return their exact total as a Fraction. Then write as_text(f), which returns f'{numerator}/{denominator}' from the fraction's numerator and denominator attributes. For example, mix(['1/3', '1/6']) is Fraction(1, 2).
from fractions import Fraction




def mix(parts):
    pass




def as_text(f):
    pass

Run your code to check it against the tests.