Financial applications and accounting systems cannot tolerate the rounding errors inherent in binary floating-point numbers. The Decimal module provides exact decimal arithmetic, ensuring that $0.10 + $0.20 equals exactly $0.30, not 0.30000000000000004.

Comparison Operations

Comparing Decimal values:

create.py
# Create Decimal

from decimal import Decimal, getcontext

# Create Decimal
print("Create Decimal:")
# From string (recommended)
d1 = Decimal('10.5')
print(f"From string: {d1}")

# From integer
d2 = Decimal(42)
print(f"From int: {d2}")

# From float (may have precision issues)
d3 = Decimal(10.5)
print(f"From float: {d3}")  # May show extra digits

# Proper way from float
d4 = Decimal(str(10.5))
print(f"From str(float): {d4}")

# Very precise numbers
precise = Decimal('3.14159265358979323846264338327950288419716939937510')
print(f"\nPrecise pi: {precise}")

# Constants
print("\nConstants:")
print(f"Decimal('0'): {Decimal('0')}")
print(f"Decimal('1'): {Decimal('1')}")
print(f"Decimal('Infinity'): {Decimal('Infinity')}")
print(f"Decimal('-Infinity'): {Decimal('-Infinity')}")
print(f"Decimal('NaN'): {Decimal('NaN')}")

# Tuple representation
print("\nTuple representation:")
# (sign, digits, exponent)
d5 = Decimal((0, (1, 2, 3), -2))  # 1.23
print(f"From tuple (0, (1, 2, 3), -2): {d5}")

d6 = Decimal((1, (5, 0), 0))  # -50
print(f"From tuple (1, (5, 0), 0): {d6}")

# Precision context
print("\nPrecision context:")
print(f"Current precision: {getcontext().prec}")
getcontext().prec = 10
result = Decimal('1') / Decimal('3')
print(f"1/3 with prec=10: {result}")

getcontext().prec = 50
result = Decimal('1') / Decimal('3')
print(f"1/3 with prec=50: {result}")

# Reset to default
getcontext().prec = 28

# Convert to other types
print("\nConvert to other types:")
d = Decimal('42.75')
print(f"Decimal: {d}")
print(f"int: {int(d)}")
print(f"float: {float(d)}")
print(f"str: {str(d)}")

# Scientific notation
print("\nScientific notation:")
d7 = Decimal('1.23E+5')
print(f"1.23E+5: {d7}")
d8 = Decimal('4.56E-3')
print(f"4.56E-3: {d8}")

# Compare with float issues
print("\nFloat vs Decimal precision:")
print(f"Float: 0.1 + 0.1 + 0.1 = {0.1 + 0.1 + 0.1}")
print(f"Decimal: 0.1 + 0.1 + 0.1 = {Decimal('0.1') + Decimal('0.1') + Decimal('0.1')}")

print(f"\nFloat: 1.1 + 2.2 = {1.1 + 2.2}")
print(f"Decimal: 1.1 + 2.2 = {Decimal('1.1') + Decimal('2.2')}")

arithmetic.py
# Arithmetic operations

from decimal import Decimal, getcontext

# Arithmetic operations
a = Decimal('10.5')
b = Decimal('3.25')

print(f"a = {a}")
print(f"b = {b}")
print()

# Addition
print("Addition:")
print(f"a + b = {a + b}")

# Subtraction
print("\nSubtraction:")
print(f"a - b = {a - b}")

# Multiplication
print("\nMultiplication:")
print(f"a * b = {a * b}")

# Division
print("\nDivision:")
print(f"a / b = {a / b}")

# Floor division
print("\nFloor division:")
print(f"a // b = {a // b}")

# Modulo
print("\nModulo:")
print(f"a % b = {a % b}")

# Power
print("\nPower:")
print(f"a ** 2 = {a ** 2}")
print(f"2 ** 10 = {Decimal('2') ** 10}")

# Negation
print("\nNegation:")
print(f"-a = {-a}")

# Absolute value
print("\nAbsolute value:")
neg = Decimal('-42.5')
print(f"abs({neg}) = {abs(neg)}")

# divmod
print("\ndivmod:")
quotient, remainder = divmod(a, b)
print(f"divmod({a}, {b}) = ({quotient}, {remainder})")

# Chaining operations
print("\nChaining:")
result = Decimal('5') * Decimal('3') + Decimal('10') - Decimal('2')
print(f"5 * 3 + 10 - 2 = {result}")

# Precision in operations
print("\nPrecision in operations:")
getcontext().prec = 10
x = Decimal('1') / Decimal('3')
print(f"1/3 with prec=10: {x}")
print(f"x * 3 = {x * Decimal('3')}")

getcontext().prec = 28  # Reset

# Square root
print("\nSquare root:")
d = Decimal('16')
print(f"sqrt({d}) = {d.sqrt()}")

d2 = Decimal('2')
print(f"sqrt({d2}) = {d2.sqrt()}")

# Exponential and log
print("\nExponential:")
d = Decimal('1')
print(f"exp({d}) = {d.exp()}")

print("\nNatural logarithm:")
d = Decimal('10')
print(f"ln({d}) = {d.ln()}")

print("\nBase-10 logarithm:")
d = Decimal('100')
print(f"log10({d}) = {d.log10()}")

# Compound calculations
print("\nCompound interest:")
principal = 
rate = Decimal('0.05')
time = 10

amount = principal * (Decimal('1') + rate) ** time
print(f"Principal: ${principal}")
print(f"Rate: {rate * 100}%")
print(f"Time: {time} years")
print(f"Amount: ${amount:.2f}")
print(f"Interest: ${amount - principal:.2f}")

# Precise division
print("\nPrecise division:")
getcontext().prec = 50
result = Decimal('1') / Decimal('7')
print(f"1/7 = {result}")
getcontext().prec = 28  # Reset

# Arithmetic operations

from decimal import Decimal, getcontext

# Arithmetic operations
a = Decimal('10.5')
b = Decimal('3.25')

print(f"a = {a}")
print(f"b = {b}")
print()

# Addition
print("Addition:")
print(f"a + b = {a + b}")

# Subtraction
print("\nSubtraction:")
print(f"a - b = {a - b}")

# Multiplication
print("\nMultiplication:")
print(f"a * b = {a * b}")

# Division
print("\nDivision:")
print(f"a / b = {a / b}")

# Floor division
print("\nFloor division:")
print(f"a // b = {a // b}")

# Modulo
print("\nModulo:")
print(f"a % b = {a % b}")

# Power
print("\nPower:")
print(f"a ** 2 = {a ** 2}")
print(f"2 ** 10 = {Decimal('2') ** 10}")

# Negation
print("\nNegation:")
print(f"-a = {-a}")

# Absolute value
print("\nAbsolute value:")
neg = Decimal('-42.5')
print(f"abs({neg}) = {abs(neg)}")

# divmod
print("\ndivmod:")
quotient, remainder = divmod(a, b)
print(f"divmod({a}, {b}) = ({quotient}, {remainder})")

# Chaining operations
print("\nChaining:")
result = Decimal('5') * Decimal('3') + Decimal('10') - Decimal('2')
print(f"5 * 3 + 10 - 2 = {result}")

# Precision in operations
print("\nPrecision in operations:")
getcontext().prec = 10
x = Decimal('1') / Decimal('3')
print(f"1/3 with prec=10: {x}")
print(f"x * 3 = {x * Decimal('3')}")

getcontext().prec = 28  # Reset

# Square root
print("\nSquare root:")
d = Decimal('16')
print(f"sqrt({d}) = {d.sqrt()}")

d2 = Decimal('2')
print(f"sqrt({d2}) = {d2.sqrt()}")

# Exponential and log
print("\nExponential:")
d = Decimal('1')
print(f"exp({d}) = {d.exp()}")

print("\nNatural logarithm:")
d = Decimal('10')
print(f"ln({d}) = {d.ln()}")

print("\nBase-10 logarithm:")
d = Decimal('100')
print(f"log10({d}) = {d.log10()}")

# Compound calculations
print("\nCompound interest:")
principal = 
rate = Decimal('0.05')
time = 10

amount = principal * (Decimal('1') + rate) ** time
print(f"Principal: ${principal}")
print(f"Rate: {rate * 100}%")
print(f"Time: {time} years")
print(f"Amount: ${amount:.2f}")
print(f"Interest: ${amount - principal:.2f}")

# Precise division
print("\nPrecise division:")
getcontext().prec = 50
result = Decimal('1') / Decimal('7')
print(f"1/7 = {result}")
getcontext().prec = 28  # Reset

# Arithmetic operations

from decimal import Decimal, getcontext

# Arithmetic operations
a = Decimal('10.5')
b = Decimal('3.25')

print(f"a = {a}")
print(f"b = {b}")
print()

# Addition
print("Addition:")
print(f"a + b = {a + b}")

# Subtraction
print("\nSubtraction:")
print(f"a - b = {a - b}")

# Multiplication
print("\nMultiplication:")
print(f"a * b = {a * b}")

# Division
print("\nDivision:")
print(f"a / b = {a / b}")

# Floor division
print("\nFloor division:")
print(f"a // b = {a // b}")

# Modulo
print("\nModulo:")
print(f"a % b = {a % b}")

# Power
print("\nPower:")
print(f"a ** 2 = {a ** 2}")
print(f"2 ** 10 = {Decimal('2') ** 10}")

# Negation
print("\nNegation:")
print(f"-a = {-a}")

# Absolute value
print("\nAbsolute value:")
neg = Decimal('-42.5')
print(f"abs({neg}) = {abs(neg)}")

# divmod
print("\ndivmod:")
quotient, remainder = divmod(a, b)
print(f"divmod({a}, {b}) = ({quotient}, {remainder})")

# Chaining operations
print("\nChaining:")
result = Decimal('5') * Decimal('3') + Decimal('10') - Decimal('2')
print(f"5 * 3 + 10 - 2 = {result}")

# Precision in operations
print("\nPrecision in operations:")
getcontext().prec = 10
x = Decimal('1') / Decimal('3')
print(f"1/3 with prec=10: {x}")
print(f"x * 3 = {x * Decimal('3')}")

getcontext().prec = 28  # Reset

# Square root
print("\nSquare root:")
d = Decimal('16')
print(f"sqrt({d}) = {d.sqrt()}")

d2 = Decimal('2')
print(f"sqrt({d2}) = {d2.sqrt()}")

# Exponential and log
print("\nExponential:")
d = Decimal('1')
print(f"exp({d}) = {d.exp()}")

print("\nNatural logarithm:")
d = Decimal('10')
print(f"ln({d}) = {d.ln()}")

print("\nBase-10 logarithm:")
d = Decimal('100')
print(f"log10({d}) = {d.log10()}")

# Compound calculations
print("\nCompound interest:")
principal = 
rate = Decimal('0.05')
time = 10

amount = principal * (Decimal('1') + rate) ** time
print(f"Principal: ${principal}")
print(f"Rate: {rate * 100}%")
print(f"Time: {time} years")
print(f"Amount: ${amount:.2f}")
print(f"Interest: ${amount - principal:.2f}")

# Precise division
print("\nPrecise division:")
getcontext().prec = 50
result = Decimal('1') / Decimal('7')
print(f"1/7 = {result}")
getcontext().prec = 28  # Reset

context.py
# Context and precision

from decimal import Decimal, getcontext, localcontext, Context, ROUND_HALF_UP, ROUND_DOWN, InvalidOperation

# Context settings
# Get current context
print("Current context:")
ctx = getcontext()
print(f"Precision: {ctx.prec}")
print(f"Rounding: {ctx.rounding}")
print(f"Emin: {ctx.Emin}")
print(f"Emax: {ctx.Emax}")
print()

# Set precision
print("Set precision:")
getcontext().prec = 10
result = Decimal('1') / Decimal('3')
print(f"1/3 with prec=10: {result}")

getcontext().prec = 50
result = Decimal('1') / Decimal('3')
print(f"1/3 with prec=50: {result}")

getcontext().prec = 28  # Reset to default

# Set rounding mode
print("\nSet rounding mode:")
getcontext().rounding = ROUND_HALF_UP
result = Decimal('2.5').quantize(Decimal('1'))
print(f"2.5 with ROUND_HALF_UP: {result}")

getcontext().rounding = ROUND_DOWN
result = Decimal('2.9').quantize(Decimal('1'))
print(f"2.9 with ROUND_DOWN: {result}")

from decimal import ROUND_HALF_EVEN
getcontext().rounding = ROUND_HALF_EVEN  # Reset to default

# Local context (temporary changes)
print("\nLocal context:")
print(f"Global precision: {getcontext().prec}")

with localcontext() as ctx:
    ctx.prec = 5
    result = Decimal('1') / Decimal('7')
    print(f"Inside local context (prec=5): {result}")

result = Decimal('1') / Decimal('7')
print(f"Outside local context: {result}")

# Create custom context
print("\nCustom context:")
custom_ctx = Context(prec=15, rounding=ROUND_DOWN)
with localcontext(custom_ctx):
    result = Decimal('1') / Decimal('3')
    print(f"Custom context (prec=15, ROUND_DOWN): {result}")

# Traps (control exceptions)
print("\nTraps:")
print(f"Default traps: {getcontext().traps}")

# Disable division by zero trap
print("\nDivision by zero:")
with localcontext() as ctx:
    ctx.traps[InvalidOperation] = False
    try:
        result = Decimal('1') / Decimal('0')
        print(f"1/0 = {result}")
        print(f"Is infinite: {result.is_infinite()}")
    except:
        print("Division by zero raised exception")

# Flags (track what happened)
print("\nFlags:")
getcontext().clear_flags()
result = Decimal('1e1000000')
print(f"Flags after overflow: {getcontext().flags}")

# Clamping
print("\nClamping:")
print(f"Clamp: {getcontext().clamp}")

# Copying context
print("\nCopying context:")
ctx1 = getcontext()
ctx2 = ctx1.copy()
ctx2.prec = 10
print(f"Original precision: {ctx1.prec}")
print(f"Copy precision: {ctx2.prec}")

# ExtendedContext (for special operations)
print("\nExtendedContext:")
from decimal import ExtendedContext
with localcontext(ExtendedContext):
    print(f"Extended context precision: {getcontext().prec}")

# Practical examples
print("\n" + "="*50)
print("Practical examples:")

# Financial calculation with controlled precision
print("\nFinancial calculation:")
with localcontext() as ctx:
    ctx.prec = 10
    ctx.rounding = ROUND_HALF_UP
    
    price = Decimal('19.99')
    tax_rate = Decimal('0.08')
    quantity = 3
    
    subtotal = price * quantity
    tax = (subtotal * tax_rate).quantize(Decimal('0.01'))
    total = subtotal + tax
    
    print(f"Price: ${price}")
    print(f"Tax rate: {tax_rate * 100}%")
    print(f"Quantity: {quantity}")
    print(f"Subtotal: ${subtotal}")
    print(f"Tax: ${tax}")
    print(f"Total: ${total:.2f}")

# Scientific calculation with high precision
print("\nScientific calculation:")
with localcontext() as ctx:
    ctx.prec = 100
    
    # Calculate e using series
    e = Decimal('1')
    factorial = Decimal('1')
    for i in range(1, 50):
        factorial *= i
        e += Decimal('1') / factorial
    
    print(f"e (100 digits): {e}")

# Currency conversion with rounding
print("\nCurrency conversion:")
with localcontext() as ctx:
    ctx.prec = 28
    ctx.rounding = ROUND_HALF_UP
    
    usd = Decimal('100.00')
    rate = Decimal('0.85')
    eur = (usd * rate).quantize(Decimal('0.01'))
    
    print(f"${usd} USD = €{eur} EUR (rate: {rate})")

rounding.py
# Rounding modes

from decimal import Decimal, getcontext, ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_05UP

# Rounding modes
value = Decimal('2.5')

print(f"Value: {value}")
print()

# quantize for rounding
print("Rounding 2.5:")
print(f"ROUND_CEILING (up): {value.quantize(Decimal('1'), rounding=ROUND_CEILING)}")
print(f"ROUND_FLOOR (down): {value.quantize(Decimal('1'), rounding=ROUND_FLOOR)}")
print(f"ROUND_UP (away from 0): {value.quantize(Decimal('1'), rounding=ROUND_UP)}")
print(f"ROUND_DOWN (toward 0): {value.quantize(Decimal('1'), rounding=ROUND_DOWN)}")
print(f"ROUND_HALF_UP: {value.quantize(Decimal('1'), rounding=ROUND_HALF_UP)}")
print(f"ROUND_HALF_DOWN: {value.quantize(Decimal('1'), rounding=ROUND_HALF_DOWN)}")
print(f"ROUND_HALF_EVEN (banker's): {value.quantize(Decimal('1'), rounding=ROUND_HALF_EVEN)}")

# Different values
print("\nRounding different values:")
values = [Decimal('2.4'), Decimal('2.5'), Decimal('2.6'), Decimal('-2.5')]
for val in values:
    print(f"{val}: HALF_UP={val.quantize(Decimal('1'), rounding=ROUND_HALF_UP)}, "
          f"HALF_EVEN={val.quantize(Decimal('1'), rounding=ROUND_HALF_EVEN)}")

# Round to decimal places
print("\nRound to decimal places:")
pi = Decimal('3.14159265358979323846')
print(f"Original: {pi}")
print(f"2 places: {pi.quantize(Decimal('0.01'))}")
print(f"4 places: {pi.quantize(Decimal('0.0001'))}")
print(f"6 places: {pi.quantize(Decimal('0.000001'))}")

# Round to significant figures using context
print("\nRound to significant figures:")
getcontext().prec = 3
large = Decimal('12345.6789')
rounded = +large  # Unary plus applies rounding
print(f"12345.6789 to 3 sig figs: {rounded}")

getcontext().prec = 5
rounded = +large
print(f"12345.6789 to 5 sig figs: {rounded}")

getcontext().prec = 28  # Reset

# Context rounding mode
print("\nContext rounding mode:")
getcontext().rounding = ROUND_UP
result = Decimal('10') / Decimal('3')
print(f"10/3 with ROUND_UP: {result}")

getcontext().rounding = ROUND_DOWN
result = Decimal('10') / Decimal('3')
print(f"10/3 with ROUND_DOWN: {result}")

getcontext().rounding = ROUND_HALF_EVEN  # Reset

# Financial rounding
print("\nFinancial rounding (2 decimals):")
prices = [Decimal('19.995'), Decimal('19.994'), Decimal('19.996')]
for price in prices:
    rounded = price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
    print(f"${price} -> ${rounded}")

# Round to nearest 5 cents
print("\nRound to nearest 5 cents:")
amounts = [Decimal('1.23'), Decimal('1.27'), Decimal('1.28')]
nickel = Decimal('0.05')
for amount in amounts:
    rounded = (amount / nickel).quantize(Decimal('1'), rounding=ROUND_HALF_UP) * nickel
    print(f"${amount} -> ${rounded}")

# Truncate (no rounding)
print("\nTruncate decimals:")
value = Decimal('123.456789')
truncated = value.quantize(Decimal('0.01'), rounding=ROUND_DOWN)
print(f"{value} truncated to 2 places: {truncated}")

# Round to nearest hundred
print("\nRound to nearest hundred:")
amount = Decimal('12345')
hundred = Decimal('100')
rounded = (amount / hundred).quantize(Decimal('1'), rounding=ROUND_HALF_UP) * hundred
print(f"{amount} -> {rounded}")

# Custom rounding
def round_decimal(value, places, mode=ROUND_HALF_UP):
    """Round Decimal to specified places."""
    if places < 0:
        # Round to tens, hundreds, etc.
        factor = Decimal(10) ** abs(places)
        return (value / factor).quantize(Decimal('1'), rounding=mode) * factor
    else:
        quantizer = Decimal(10) ** -places
        return value.quantize(quantizer, rounding=mode)

print("\nCustom rounding function:")
val = Decimal('12345.6789')
print(f"Original: {val}")
print(f"2 places: {round_decimal(val, 2)}")
print(f"0 places: {round_decimal(val, 0)}")
print(f"-2 places (hundreds): {round_decimal(val, -2)}")

comparison.py
# Comparison operations

from decimal import Decimal

# Comparison
a = Decimal('10.5')
b = Decimal('20.3')
c = Decimal('10.5')

print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")
print()

# Equality
print("Equality:")
print(f"a == b: {a == b}")
print(f"a == c: {a == c}")
print(f"a != b: {a != b}")

# Ordering
print("\nOrdering:")
print(f"a < b: {a < b}")
print(f"a <= b: {a <= b}")
print(f"a > b: {a > b}")
print(f"a >= b: {a >= b}")

# compare method
print("\ncompare method:")
print(f"a.compare(b): {a.compare(b)}")  # -1, 0, or 1
print(f"b.compare(a): {b.compare(a)}")
print(f"a.compare(c): {a.compare(c)}")

# compare_signal (raises on NaN)
print("\ncompare_signal:")
print(f"a.compare_signal(b): {a.compare_signal(b)}")

# max and min
print("\nmax and min:")
print(f"max(a, b): {max(a, b)}")
print(f"min(a, b): {min(a, b)}")

# Decimal max/min methods
print("\nDecimal max/min methods:")
print(f"a.max(b): {a.max(b)}")
print(f"a.min(b): {a.min(b)}")

# Find max/min in list
print("\nFind max/min in list:")
numbers = [Decimal('12.5'), Decimal('45.2'), Decimal('23.7'), Decimal('89.1')]
print(f"Numbers: {numbers}")
print(f"Max: {max(numbers)}")
print(f"Min: {min(numbers)}")

# Sort
print("\nSort:")
sorted_nums = sorted(numbers)
print(f"Sorted: {sorted_nums}")

# Check if in range
print("\nCheck if in range:")
value = Decimal('15.5')
lower = Decimal('10')
upper = Decimal('20')
in_range = lower <= value <= upper
print(f"{value} in [{lower}, {upper}]: {in_range}")

# Zero checks
print("\nZero checks:")
zero = Decimal('0')
pos = Decimal('5')
neg = Decimal('-5')

print(f"{zero} == 0: {zero == 0}")
print(f"{pos} > 0: {pos > 0}")
print(f"{neg} < 0: {neg < 0}")

# is_zero method
print("\nis_zero method:")
print(f"{zero}.is_zero(): {zero.is_zero()}")
print(f"{pos}.is_zero(): {pos.is_zero()}")

# Sign checks
print("\nSign checks:")
print(f"{pos}.is_signed(): {pos.is_signed()}")
print(f"{neg}.is_signed(): {neg.is_signed()}")
print(f"{zero}.is_signed(): {zero.is_signed()}")

# is_finite, is_infinite, is_nan
print("\nSpecial value checks:")
finite = Decimal('42.5')
infinity = Decimal('Infinity')
nan = Decimal('NaN')

print(f"{finite}.is_finite(): {finite.is_finite()}")
print(f"{infinity}.is_infinite(): {infinity.is_infinite()}")
print(f"{nan}.is_nan(): {nan.is_nan()}")

# is_normal, is_subnormal
print("\nis_normal:")
normal = Decimal('1.5')
tiny = Decimal('1e-1000')
print(f"{normal}.is_normal(): {normal.is_normal()}")
print(f"{tiny}.is_normal(): {tiny.is_normal()}")

# compare_total (considers exponent)
print("\ncompare_total (considers exponent):")
d1 = Decimal('1.0')
d2 = Decimal('1.00')
print(f"d1 = {d1}, d2 = {d2}")
print(f"d1 == d2: {d1 == d2}")  # True (value equal)
print(f"d1.compare_total(d2): {d1.compare_total(d2)}")  # -1 (exponents differ)

# Tolerance comparison (for floats converted to Decimal)
print("\nTolerance comparison:")
def approx_equal(a, b, tolerance=Decimal('0.0001')):
    """Check if two Decimals are approximately equal."""
    return abs(a - b) <= tolerance

val1 = Decimal('3.14159')
val2 = Decimal('3.14160')
print(f"approx_equal({val1}, {val2}): {approx_equal(val1, val2)}")
print(f"approx_equal({val1}, {val2}, 0.00001): {approx_equal(val1, val2, Decimal('0.00001'))}")

Decimal A numeric type that represents decimal numbers exactly as humans write them, avoiding binary floating-point representation errors.
string initialization Creating Decimal from strings like Decimal('0.1') preserves exact values, while Decimal(0.1) inherits float imprecision.
getcontext() The decimal context controls precision, rounding mode, and error handling for all Decimal operations in the current thread.

Exercise: practical.py

Calculate compound interest and format currency values with proper rounding