Tuesday, December 1, 2020

LAAC Technology: Float vs Decimal in Python

Table of Contents

Introduction

Both the float and decimal types store numerical values in Python, and at the beginning, choosing when to use each can be confusing. Python’s decimal documentation is a good starting point to learn when to use decimals. Generally, decimals exist in Python to solve the precision issues of floats.

Floats

Use floats when convenience and speed matter. A float gives you an approximation of the number you declare. For example, if I print 0.1 with 18 decimals places, I don’t actually get 0.1 but instead an approximation.

>>> print(f"{0.1:.18f}")
0.100000000000000006

Similarly, when doing operations, such as addition with floats, you get an approximation, which can lead confusing code, such as the following.

>>> .1 + .1 + .1 == .3
False
>>> .1 + .1 + .1
0.30000000000000004

Intuitively, the addition makes sense, and at a glance, you expect the statement to be true. However, because of the float approximation it turns out to be false. This demonstrates one of the big issues with floats which is the lack of reliable equality testing. To fix this equality test without the use of decimals we need to use rounding.

>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
>>> round(.1 + .1 + .1, 10)
0.3

In this case, we round the floats to prevent any precision issues. If you find yourself using floats and rounding frequently in your codebase, this indicates that it’s time to use decimals.

Decimals

Use decimals when precision matters, such as with financial calculations. But realistically, I try to always use decimals. The performance difference between float and decimal, with Python 3, is not outlandish, and in my experience, the precision benefits of a decimal outweigh the performance benefits of a float.

Let’s look at the previous examples with decimals instead of floats.

>>> from decimal import Decimal
>>> print(f"{Decimal('0.1'):.18f}")
0.100000000000000000
>>> Decimal('.1') + Decimal('.1') + Decimal('.1') == Decimal('.3')
True

Using decimals in these examples prevents the subtle bugs introduced by floats. If you notice, the decimals use strings for initialization. Once again, using floats causes precision issues.

>>> from decimal import Decimal
>>> Decimal(0.01) == Decimal("0.01")
False

In this example, we expect these decimals to be equal, but, because of the precision issues with floats, this decimal equality test returns false. If we look at each of these decimals, we’ll see why.

>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> Decimal("0.01")
Decimal('0.01')

The decimal declared as a float is not technically 0.01, which results in an equality test of false. All decimals should be initialized using strings to prevent precision issues. If decimals aren’t initialized with strings, we lose some of the precision benefits of decimals and create subtle bugs.

Final Thoughts

I just use decimals. Even though decimals aren’t as convenient due to the extra imports, and aren’t as performant, the benefits of preventing subtle bugs introduced by a float’s precision issues outweigh the downsides. Decimals come with their own subtle issues, but initializing them with strings prevents most of those issues.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...