>

# Representing money in Python

Python’s `float` type is a natural first step to represent monetary amounts in the code. Almost all platforms map Python floats to IEEE-754 “double precision”.

Doubles contain 53 bits of precision. When the machine is trying to represent the fractional part (mantissa) of a given number it finds a bit sequence `\(b_1, b_2 ... b_{53}\)` so that a sum:

is close to the number as possible. So, values such as 0.1 cannot be exactly represented. You may find this famous example in official Python docs:

``````>>> .1 + .1 + .1 == .3
False
``````

It happens because 0.1 is not exactly 1/10.

Let’s see how it can hinder money operations.

A web shop sells an item for 1.01. The web shop buys it form its supplier for 0.99 and the owner wants to calculate a revenue if quintillion items (one with 18 zeros) are sold:

``````format((1.01 - 0.99)*1e18, '.2f')
>>>  '20000000000000016.00'
``````

You expected a 2 with 16 zeros (2 cent revenue per item times 1e18), but there are an additional 16 dollars. It will take a lot of time to sell so many items, but the calculation is wrong anyway.

More real life example

You have a deposit of 93 cents in a bank with 2.25% interest rate. You came back in thousand years to withdraw it. Happened to this guy: For that you need to calculate a future value:

``````deposit = 0.93
future_value = deposit * ((1 + (0.0225))**1000)
future_value
>>> 4283508449.7111807
``````

Almost 4.3 billion. But is it accurate since we are using float here?

Decimal

It’s a part of a standard library and provides a representation of real numbers. The web shop revenue calculation is now correct:

``````from decimal import Decimal
sell_price = Decimal('1.01')
items_sold = Decimal('1e18')
profit = (sell_price - buy_price) * items_sold
profit
>>> Decimal('2E+16')
``````

And for future value:

``````deposit = Decimal('0.93')
decimal_future_value = deposit * (1 + (Decimal('0.0225')))**Decimal('1000')
decimal_future_value
>>> Decimal('4283508449.711328779924154334')
``````

The difference with float in that case is quite small - 4th digit after decimal point. But it’s still present.

Performance

Float also appears more fast than Decimal:

``````In : def float_order(item_price, item_count):
...:     return sum([item_price for _ in range(item_count)])
...:

In : def decimal_order(item_price, item_count):
...:     decimal_item_price = Decimal(item_price)
...:     return sum([decimal_item_price for _ in range(item_count)])
...:

In : %timeit float_order(1.01, 10000)
297 µs ± 11.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In : from decimal import Decimal

In : %timeit decimal_order('1.01', 10000)
783 µs ± 19.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
``````

Have you ever experiences problems when working with monetary values represented by `float`? Connect with me on LinkedIn

Support the author - Buy me a coffee!