Even the most basic mathematical operations can sometimes give an erroneous result. This happens due to limitations in storing the exact value of some numbers.
Mathematical Modules in Python: Decimal and Fractions
You can overcome these limitations by using the decimal module in Python. Similarly, neither the math nor the cmath module that we learned about in our last tutorial can help us in doing fraction-based arithmetic. However, the fractions module in Python does exactly that.
In this tutorial, you will learn about both these modules and the different functions they make available.
Why Do We Need a Decimal Module?
You are probably wondering why we need a module to do basic arithmetic with decimal numbers when we can already do the same using floats.
Before I answer this question, I want you to take a guess about the output value if you type
0.1 + 0.2 in the Python console. If you guessed that the output should be 0.3, you will be surprised when you check out the actual result, which is 0.30000000000000004. You can try some other calculation like
0.05 + 0.1 and you will get 0.15000000000000002.
To understand what’s going on here, try to represent
1/3 in decimal form, and you will notice that the number is actually non-terminating in base 10. Similarly, some numbers like 0.1 or 1/10 are non-terminating in base 2. Since these numbers still need to be represented somehow, a few approximations are made while storing them, which results in those errors.
The number 0.30000000000000004 is actually very close to 0.3, so we can get away with this approximation most of the time. Unfortunately, this approximation is not going to cut it when you are simulating a satellite launch or dealing with money. Another problem with these approximations is that the errors keep piling up.
To get precise results like the ones we are used to dealing with when doing calculations by hand, we need something that supports fast, correctly rounded, decimal floating point arithmetic, and the decimal module does exactly that.
Using the Decimal Module
Before using the module, you need to import it first. After that, you can create decimals from integers, strings, floats, or tuples. When the decimal is constructed from an integer or a float, there is an exact conversion of the value of that number. Take a look at the examples below to see what I mean:
from decimal import Decimal Decimal(121) # returns Decimal('121') Decimal(0.05) # returns Decimal('0.05000000000000000277555756') Decimal('0.05') # returns Decimal('0.05') Decimal((0, (8, 3, 2, 4), -3)) # returns Decimal('8.324') Decimal((1, (8, 3, 2, 4), -1)) # returns Decimal('-832.4')
As you can see, the value of
Decimal(0.05) is slightly different from
Decimal('0.05'). This means that when you add 0.05 and 0.1, you should use
decimal.Decimal('0.1') to construct the decimals.
from decimal import Decimal Decimal('0.05') + Decimal('0.1') # returns Decimal('0.15') Decimal(0.05) + Decimal(0.1) # returns Decimal('0.1500000000000000083266726847')
Now that you can perform various operations on decimals, you might want to control the precision or rounding for those operations. This can be done by using the
getcontext() function. This function allows you to get as well as set the value of the precision and rounding options, among other things.
Please keep in mind that both rounding and precision come into play only during arithmetic operations and not while you are creating the decimals themselves.
import decimal from decimal import Decimal, getcontext Decimal(1) / Decimal(13) # returns Decimal('0.07692307692307692307692307692') getcontext().prec = 10 Decimal(0.03) # returns Decimal('0.02999999999999999888977697537') Decimal(1) / Decimal(7) # returns Decimal('0.1428571429') getcontext().rounding = decimal.ROUND_DOWN Decimal(1) / Decimal(7) # returns Decimal('0.1428571428')
You can also use some of the mathematical functions like
log() with decimals. Here are a few examples:
import decimal from decimal import Decimal, getcontext Decimal(2).sqrt() # returns Decimal('1.414213562373095048801688724') getcontext().prec = 4 Decimal('2').sqrt() # returns Decimal('1.414') Decimal('2000').log10() # returns Decimal('3.301')
Using the Fractions Module
Sometimes, you might face situations where you need to perform various operations on fractions or the final result needs to be a fraction. The fractions module can be of great help in these cases. It allows you to create a
Fraction instance from numbers, floats, decimals, and even strings. Just like the decimal module, there are a few issues with this module as well when it comes to creating fractions from floats. Here are a few examples:
from fractions import Fraction from decimal import Decimal Fraction(11, 35) # returns Fraction(11, 35) Fraction(10, 18) # returns Fraction(5, 9) Fraction('8/25') # returns Fraction(8, 25) Fraction(1.13) # returns Fraction(1272266894732165, 1125899906842624) Fraction('1.13') # returns Fraction(113, 100) Fraction(Decimal('1.13')) # returns Fraction(113, 100)
You can also perform simple mathematical operations like addition and subtraction on fractions just like regular numbers.
from fractions import Fraction Fraction(113, 100) + Fraction(25, 18) # returns Fraction(2267, 900) Fraction(18, 5) / Fraction(18, 10) # returns Fraction(2, 1) Fraction(18, 5) * Fraction(16, 19) # returns Fraction(288, 95) Fraction(18, 5) * Fraction(15, 36) # returns Fraction(3, 2) Fraction(12, 5) ** Fraction(12, 10) # returns 2.8592589556010197
The module also has a few important methods like
limit_denominator(max_denominator) which will find and return a fraction closest in value to the given fraction whose denominator is at most
max_denominator. You can also return the numerator of a given fraction in the lowest term by using the
numerator property and the denominator by using the
from fractions import Fraction Fraction('3.14159265358979323846') # returns Fraction(157079632679489661923, 50000000000000000000) Fraction('3.14159265358979323846').limit_denominator(10000) # returns Fraction(355, 113) Fraction('3.14159265358979323846').limit_denominator(100) # returns Fraction(311, 99) Fraction('3.14159265358979323846').limit_denominator(10) # returns Fraction(22, 7) Fraction(125, 50).numerator # returns 5 Fraction(125, 50).denominator # returns 2
You can also use this module with various functions in the math module to perform fraction-based calculations.
import math from fractions import Fraction math.sqrt(Fraction(25, 4)) # returns 2.5 math.sqrt(Fraction(28,3)) # returns 3.0550504633038935 math.floor(Fraction(3558, 1213)) # returns 2 Fraction(math.sin(math.pi/3)) # returns Fraction(3900231685776981, 4503599627370496) Fraction(math.sin(math.pi/3)).limit_denominator(10) # returns Fraction(6, 7)
These two modules should be sufficient to help you perform common operations on both decimals and fractions. As shown in the last section, you can use these modules along with the math module to calculate the value of all kinds of mathematical functions in the format you desire.
In the next tutorial of the series, you will learn about the random module in Python.