Math #

Python provides several modules for numerical computing, each with a different purpose. The math module handles common float-based mathematical operations, decimal for calculations requiring high precision like finance, fractions for exact rational number arithmetic, and statistics for basic data analysis. Understanding the differences between these four modules is important so you don’t get stuck using the wrong tool for different problems.

Why Floats Can Be Problematic #

Before diving into the modules, it’s important to understand one Python behavior that often surprises beginners: float arithmetic isn’t always exact.

# Surprising float behavior
print(0.1 + 0.2)          # 0.30000000000000004  -- not 0.3!
print(0.1 + 0.2 == 0.3)   # False

# This isn't a Python bug -- it's the nature of IEEE 754 floats in every language
# ANTI-PATTERN: comparing floats directly
if total_harga == 150000.0:    # can be wrong due to rounding errors
    pass

# CORRECT: use a tolerance or the math.isclose() module
import math
if math.isclose(total_harga, 150000.0, rel_tol=1e-9):
    pass

# Or use the decimal module for financial calculations (covered below)
Don’t use float for financial calculations or anything requiring exact decimal precision. Use the decimal module instead.

The math Module — General Mathematical Operations #

The math module provides mathematical functions operating on float types. Suitable for scientific computation, geometry, and general technical calculations.

Rounding and Absolute Value #

import math

# Various ways to round
print(math.ceil(4.2))     # 5   -- round up
print(math.ceil(-4.2))    # -4  -- up toward zero
print(math.floor(4.8))    # 4   -- round down
print(math.floor(-4.8))   # -5  -- away from zero
print(math.trunc(4.8))    # 4   -- truncate toward zero
print(math.trunc(-4.8))   # -4  -- truncate toward zero

# The difference between trunc and floor for negative numbers:
# trunc(-4.8) = -4  (toward zero)
# floor(-4.8) = -5  (down, away from zero)

# Absolute value
print(math.fabs(-5.5))    # 5.5  -- always float
print(abs(-5.5))          # 5.5  -- Python built-in, can be int or float

Roots, Powers, and Logarithms #

import math

# Roots and powers
print(math.sqrt(16))        # 4.0     -- square root
print(math.isqrt(17))       # 4       -- integer square root (rounded down)
print(math.pow(2, 10))      # 1024.0  -- always float
print(2 ** 10)              # 1024    -- built-in operator, can be int

# Logarithms
print(math.log(8, 2))       # 3.0  -- log base 2 of 8
print(math.log(math.e))     # 1.0  -- natural log (base e)
print(math.log10(1000))     # 3.0  -- log base 10
print(math.log2(1024))      # 10.0 -- log base 2 (more precise than log(x, 2))

# Exponentials
print(math.exp(1))          # 2.718...  -- e^1
print(math.exp(2))          # 7.389...  -- e^2

Trigonometric Functions #

All trigonometric functions in math work in radians, not degrees. Use math.radians() and math.degrees() for conversion.

import math

# Angle conversion
print(math.radians(180))    # 3.141592...  -- degrees to radians
print(math.degrees(math.pi))  # 180.0      -- radians to degrees

# Basic trigonometric functions (input in radians)
print(math.sin(math.pi / 2))  # 1.0   -- sin 90°
print(math.cos(math.pi))      # -1.0  -- cos 180°
print(math.tan(math.pi / 4))  # 1.0   -- tan 45°

# Inverse trigonometry (output in radians)
print(math.asin(1))           # 1.5707...  -- π/2, i.e., 90°
print(math.acos(-1))          # 3.1415...  -- π, i.e., 180°
print(math.atan(1))           # 0.7853...  -- π/4, i.e., 45°

# atan2 -- safer than atan for determining the quadrant
print(math.atan2(1, 1))   # 0.7853... -- angle from point (1,1) to the origin
print(math.atan2(-1, 1))  # -0.785...  -- angle from point (1,-1) to the origin

Combinatorial Functions #

import math

# Factorial
print(math.factorial(5))    # 120   -- 5! = 5×4×3×2×1
print(math.factorial(0))    # 1     -- 0! = 1 by definition

# Combination C(n, k) -- choosing k items from n without order
print(math.comb(5, 2))      # 10    -- C(5,2)
print(math.comb(10, 3))     # 120

# Permutation P(n, k) -- choosing k items from n with order
print(math.perm(5, 2))      # 20    -- P(5,2)
print(math.perm(5))         # 120   -- P(5,5) = 5!

Utility Functions #

import math

# Euclidean distance / vector length
print(math.hypot(3, 4))            # 5.0   -- √(3²+4²)
print(math.hypot(1, 1, 1))         # √3    -- supports n dimensions (Python 3.8+)

# GCD and LCM
print(math.gcd(12, 8))             # 4     -- greatest common divisor
print(math.lcm(4, 6))              # 12    -- least common multiple (Python 3.9+)
print(math.gcd(12, 8, 6))          # 2     -- GCD of more than two numbers (Python 3.9+)

# Float comparison with tolerance
print(math.isclose(0.1 + 0.2, 0.3))         # True  -- with the default relative tolerance
print(math.isclose(1000.0, 1001.0, rel_tol=0.01))  # True  -- 1% tolerance
print(math.isclose(0.0, 1e-10, abs_tol=1e-9))      # True  -- absolute tolerance

# Check special float values
print(math.isfinite(1.0))         # True
print(math.isfinite(math.inf))    # False
print(math.isinf(math.inf))       # True
print(math.isnan(math.nan))       # True
print(math.isnan(float("nan")))   # True

Mathematical Constants #

import math

print(math.pi)    # 3.141592653589793   -- π
print(math.e)     # 2.718281828459045   -- Euler's number
print(math.tau)   # 6.283185307179586   -- τ = 2π
print(math.inf)   # inf                 -- positive infinity
print(math.nan)   # nan                 -- Not a Number

The decimal Module — High Precision #

The decimal module implements decimal arithmetic with controllable precision. This is the right solution for financial calculations, accounting, or anything that must not have rounding errors.

from decimal import Decimal, getcontext

# Direct comparison
print(0.1 + 0.2)                        # 0.30000000000000004  -- regular float
print(Decimal("0.1") + Decimal("0.2"))  # 0.3  -- exact precision

# ANTI-PATTERN: creating a Decimal from a float
print(Decimal(0.1))   # 0.1000000000000000055511151231257827021181583404541015625
# -- the float already lost precision before entering Decimal!

# CORRECT: create a Decimal from a string
print(Decimal("0.1"))   # 0.1  -- precision preserved

Precision Control and Rounding #

from decimal import Decimal, getcontext, ROUND_HALF_UP, ROUND_DOWN

# Set the global precision (number of significant digits)
getcontext().prec = 10

hasil = Decimal("1") / Decimal("3")
print(hasil)   # 0.3333333333  -- 10 significant digits

# Rounding with a specific method
harga = Decimal("1234.5678")
print(harga.quantize(Decimal("0.01")))                      # 1234.57  -- ROUND_HALF_EVEN (default)
print(harga.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))  # 1234.57
print(harga.quantize(Decimal("0.01"), rounding=ROUND_DOWN))     # 1234.56

# Finance example: calculate the total purchase
def hitung_total(harga_satuan: str, jumlah: int, pajak_persen: str) -> Decimal:
    harga = Decimal(harga_satuan)
    pajak = Decimal(pajak_persen) / Decimal("100")
    subtotal = harga * jumlah
    total = subtotal * (1 + pajak)
    return total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

print(hitung_total("99.99", 3, "11"))   # 332.97
Always create a Decimal from a string, not from a float. Decimal("0.1") produces exact 0.1, while Decimal(0.1) inherits the float’s imprecision.

The fractions Module — Rational Numbers #

The fractions module represents numbers as exact fractions (numerator/denominator). Useful for mathematical calculations needing exact results without floating point errors.

from fractions import Fraction

# Create a Fraction from various sources
print(Fraction(1, 3))          # 1/3
print(Fraction("3/7"))         # 3/7
print(Fraction("0.25"))        # 1/4  -- convert from a decimal string
print(Fraction(0.25))          # 1/4  -- safe because 0.25 is exact in float

# Exact arithmetic
a = Fraction(1, 3)
b = Fraction(1, 6)
print(a + b)    # 1/2  -- not 0.4999... or 0.5000001
print(a * b)    # 1/18
print(a - b)    # 1/6
print(a / b)    # 2

# Access the numerator and denominator
f = Fraction(5, 6)
print(f.numerator)     # 5
print(f.denominator)   # 6

# Fractions are automatically simplified
print(Fraction(6, 4))   # 3/2  -- not 6/4
print(Fraction(10, 5))  # 2    -- not 10/5

The statistics Module — Basic Data Analysis #

The statistics module provides ready-made descriptive statistics functions without needing extra libraries like NumPy.

import statistics

data = [4, 8, 6, 5, 3, 2, 8, 9, 2, 5]

# Measures of central tendency
print(statistics.mean(data))        # 5.2     -- average
print(statistics.median(data))      # 5.0     -- middle value (sorted first)
print(statistics.mode([1,1,2,3]))   # 1       -- most frequent value
print(statistics.multimode([1,1,2,2,3]))  # [1, 2]  -- all modes (Python 3.8+)

# Measures of spread
print(statistics.stdev(data))       # 2.393...  -- sample standard deviation
print(statistics.pstdev(data))      # 2.270...  -- population standard deviation
print(statistics.variance(data))    # 5.733...  -- sample variance
print(statistics.pvariance(data))   # 5.16      -- population variance

# Quantiles (Python 3.8+)
print(statistics.quantiles(data, n=4))  # quartiles Q1, Q2, Q3

When to Use statistics vs Other Libraries #

Simple data analysis, no external dependencies?
  ✓ Use Python's built-in statistics module

Large datasets, performance matters, or need more functions?
  ✓ Use NumPy (pip install numpy)

Data analysis with DataFrames, groupby, or visualization?
  ✓ Use pandas (pip install pandas)

Machine learning or advanced statistics?
  ✓ Use scipy.stats (pip install scipy)

Choosing the Right Module #

General mathematical calculations (trigonometry, logarithms, roots)?
  ✓ Use the math module

Financial calculations or needing decimal precision control?
  ✓ Use the decimal module -- always create from a string, not a float

Need exact fraction results (1/3, 7/8)?
  ✓ Use the fractions module

Basic statistical analysis (mean, median, stdev)?
  ✓ Use the statistics module

Need to compare two float values?
  ✓ Use math.isclose(), not == directly

Summary #

  • Floats aren’t always exact0.1 + 0.2 != 0.3 is a property of IEEE 754, not a bug. Use math.isclose() to compare floats, not ==.
  • The math module for general mathematical operations: rounding (ceil, floor, trunc), roots and powers (sqrt, pow, isqrt), logarithms (log, log2, log10), trigonometry, and combinatorics (comb, perm, factorial).
  • Trigonometric functions work in radians — use math.radians() and math.degrees() for conversion.
  • math.atan2(y, x) is safer than math.atan(y/x) because it handles all quadrants correctly.
  • The decimal module for financial calculations — always create from a string (Decimal("0.1")), not from a float (Decimal(0.1)).
  • The fractions module for exact fractional arithmetic with no rounding errors at all.
  • The statistics module for basic data analysis without external dependencies; consider NumPy or pandas for bigger needs.

← Previous: IO   Next: Collections →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact