How To Stop Floating Point Arithmetic Errors in Python
Learn to use the Decimal library
We expect precision, consistency, and accuracy when we code. After all, it’s a computer doing the work. But your arithmetic may have been off the entire time and you didn’t even know.
If you’ve experienced floating point arithmetic errors, then you know what we’re talking about. If you’re unsure what that means, let’s show instead of tell.print(1.1 * 3) # 3.3000000000000003
This happens because decimal values are actually stored as a formula and do not have an exact representation.
We’re going to go over a solution to these inconsistencies, using a natively available library called Decimal.
Learn to Use the Decimal Library
According to the official Python documentation:
The decimal module provides support for fast correctly-rounded decimal floating point arithmetic.
So how do we go about using this readily available tool? Let’s start by importing the library. There are multiple components to import so we’ll use the * symbol.from decimal import *
Next, we’ll use the Decimal() constructor with a string value to create a new object and try our arithmetic again.print(Decimal('1.1') * 3) # 3.3
Make sure to use a string value, because otherwise the floating point number 1.1 will be converted to a Decimal object, effectively preserving the error and probably compounding it even worse than if floating point was used.print(Decimal(1.1) * 3) # 3.300000000000000266453525910
The Golden Rule
You can basically use the decimal objects as you would any other numeric value. However, there is one golden rule we have for those who choose to adopt the decimal library: do not mix and match decimal with float.
If you treat floats and decimals as interchangeable, then you’re likely to run into errors. The two data types are incompatible when it comes to arithmetic.a = Decimal('1.1')
b = 2.2
c = a + b
# TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
Beyond this golden rule, here are some tips and tricks for using Decimal().
Use .quantize() for rounding
Pass a decimal object with the appropriate number of decimal places. This is helpful when working with currency. In our example we’ll round a value to two decimal places.a = Decimal('1.123456789')
b = a.quantize(Decimal('1.00'))
print(b) # 1.12
Use getcontext() to set precision
The decimal precision can be customized by modifying the default context. First let’s look at the default context then demonstrate what happens when we make modifications.from decimal import *print(getcontext())
"""
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
"""print(Decimal(1)/Decimal(3))
# 0.3333333333333333333333333333getcontext().prec = 4print(Decimal(1)/Decimal(3))
# 0.3333
Watch out for negative modulus
The modulus operator (%) returns the remainder of a division operation. Normally, the sign of the divisor is preserved when using a negative number.print((-7) % 4) # 1
print(7 % (-4)) # -1
However, the sign of the numerator is preserved with a decimal object.print(Decimal(-7) % 4) # -3
print(7 % Decimal(-4)) # 3
Thanks for reading. Please share your experiences, questions, and comments below!