Decimal
for Monetary AmountsIn this section, we introduce Decimal
capabilities for precise monetary calculations. If you enter banking or other fields that require the accuracy provided by type Decimal
, you should investigate Decimal
’s capabilities in depth.
For most scientific and other mathematical applications that use numbers with decimal points, Python’s built-in floating-point numbers work well. For example, when we speak of a “normal” body temperature of 98.6, we do not need to be precise to a large number of digits. When we view the temperature on a thermometer and read it as 98.6, the actual value may be 98.5999473210643. The point here is that calling this number 98.6 is adequate for most body-temperature applications.
Floating-point values are stored in binary format (we introduced binary in the first chapter and discuss it in depth in the online “Number Systems” appendix). Some floating-point values are represented only approximately when they’re converted to binary. For example, consider the variable amount
with the dollars-and-cents value 112.31
. If you display amount
, it appears to have the exact value you assigned to it:
In [1]: amount = 112.31
In [2]: print(amount)
112.31
However, if you print amount
with 20
digits of precision to the right of the decimal point, you can see that the actual floating-point value in memory is not exactly 112.31
—it’s only an approximation:
In [3]: print(f'{amount:.20f}')
112.31000000000000227374
Many applications require precise representation of numbers with decimal points. Institutions like banks that deal with millions or even billions of transactions per day have to tie out their transactions “to the penny.” Floating-point numbers can represent some but not all monetary amounts with to-the-penny precision.
The Python Standard Library2 provides many predefined capabilities you can use in your Python code to avoid “reinventing the wheel.” For monetary calculations and other applications that require precise representation and manipulation of numbers with decimal points, the Python Standard Library provides type Decimal
, which uses a special coding scheme to solve the problem of to-the-penny precision. That scheme requires additional memory to hold the numbers and additional processing time to perform calculations but provides the to-the-penny precision required for monetary calculations. Banks also have to deal with other issues such as using a fair rounding algorithm when they’re calculating daily interest on accounts. Type Decimal
offers such capabilities.3
Decimal
from the decimal
ModuleWe’ve used several built-in types—int
(for integers, like 10
), float
(for floating-point numbers, like 7.5
) and str
(for strings like 'Python'
). The Decimal
type is not built into Python. Rather, it’s part of the Python Standard Library, which is divided into modules—groups of related capabilities. The decimal
module defines type Decimal
and its capabilities.
To use capabilities from a module, you must first import
the entire module, as in
import decimal
and refer to the Decimal
type as decimal.Decimal
, or you must indicate a specific capability to import using from…import
, as we do here:
In [4]: from decimal import Decimal
This import
s only the type Decimal
from the decimal
module so that you can use it in your code. We’ll discuss other import
forms beginning in the next chapter.
Decimal
sYou typically create a Decimal
from a string:
In [5]: principal = Decimal('1000.00')
In [6]: principal
Out[6]: Decimal('1000.00')
In [7]: rate = Decimal('0.05')
In [8]: rate
Out[8]: Decimal('0.05')
We’ll soon use the variables principal
and rate
in a compound-interest calculation.
Decimal
ArithmeticDecimal
s support the standard arithmetic operators +
, -
, *
, /
, //
, **
and %
, as well as the corresponding augmented assignments:
In [9]: x = Decimal('10.5')
In [10]: y = Decimal('2')
In [11]: x + y
Out[11]: Decimal('12.5')
In [12]: x // y
Out[12]: Decimal('5')
In [13]: x += y
In [14]: x
Out[14]: Decimal('12.5')
You may perform arithmetic between Decimal
s and integers, but not between Decimal
s and floating-point numbers.
Let’s compute compound interest using the Decimal
type for precise monetary calculations. Consider the following requirements statement:
A person invests $1000 in a savings account yielding 5% interest. Assuming that the person leaves all interest on deposit in the account, calculate and display the amount of money in the account at the end of each year for 10 years. Use the following formula for determining these amounts:
a = p(1 + r)n
where
p is the original amount invested (i.e., the principal),
r is the annual interest rate,
n is the number of years and
a is the amount on deposit at the end of the nth year.
To solve this problem, let’s use variables principal
and rate
that we defined in snippets [5]
and [7]
, and a for
statement that performs the interest calculation for each of the 10 years the money remains on deposit. For each year
, the loop displays a formatted string containing the year number and the amount
on deposit at the end of that year:
In [15]: for year in range(1, 11):
...: amount = principal * (1 + rate) ** year
...: print(f'{year:>2}{amount:>10.2f}')
...:
1 1050.00
2 1102.50
3 1157.62
4 1215.51
5 1276.28
6 1340.10
7 1407.10
8 1477.46
9 1551.33
10 1628.89
The algebraic expression (1 + r) n from the requirements statement is written as
(1 + rate) ** year
where variable rate
represents r and variable year
represents n.
The statement
print(f'{year:>2}{amount:>10.2f}')
uses an f-string with two placeholders to format the loop’s output.
The placeholder
{year:>2}
uses the format specifier >2
to indicate that year
’s value should be right aligned (>) in a field of width 2
—the field width specifies the number of character positions to use when displaying the value. For the single-digit year
values
1
–
9
, the format specifier
>2
displays a space character followed by the value, thus right aligning the
year
s in the first column. The following diagram shows the numbers 1 and 10 each formatted in a field width of 2:
You can left align values with <.
The format specifier 10.2f
in the placeholder
{amount:>10.2f}
formats amount
as a floating-point number (f
) right aligned (>
) in a field width of 10
with a decimal point and two digits to the right of the decimal point (.2
). Formatting the amount
s this way aligns their decimal points vertically, as is typical with monetary amounts. In the 10 character positions, the three rightmost characters are the number’s decimal point followed by the two digits to its right. The remaining seven character positions are the leading spaces and the digits to the decimal point’s left. In this example, all the dollar amounts have four digits to the left of the decimal point, so each number is formatted with three leading spaces. The following diagram shows the formatting for the value 1050.00
:
(Fill-In) A field width specifies the ___________ to use when displaying a value.
Answer: number of character positions.
(IPython Session) Assume that the tax on a restaurant bill is 6.25% and that the bill amount is $37.45. Use type Decimal
to calculate the bill total, then print the result with two digits to the right of the decimal point.
Answer:
In [1]: from decimal import Decimal
In [2]: print(f"{Decimal('37.45') * Decimal('1.0625'):.2f}")
39.79