Skip to main content Brad's PyNotes

PEP 485: A Function for Testing Approximate Equality

TL;DR

PEP 485 introduced math.isclose() to solve the fundamental problem of comparing floating-point numbers by testing if two values are “approximately equal” rather than exactly equal.

Interesting!

The default relative tolerance (1e-9) was carefully chosen to be about half of float precision, and the absolute tolerance defaults to 0.0 to force developers to consciously think about appropriate tolerance values for their specific use case.

The Floating-Point Dilemma

python code snippet start

import math

# The classic floating-point problem
print(0.1 + 0.2 == 0.3)  # False - surprise!
print(0.1 + 0.2)         # 0.30000000000000004

# This is where math.isclose() shines
print(math.isclose(0.1 + 0.2, 0.3))  # True

python code snippet end

Before PEP 485, developers had to roll their own comparison functions, leading to inconsistent and often incorrect implementations.

The Algorithm Behind the Magic

python code snippet start

# The isclose() algorithm (simplified):
# abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

import math

def demonstrate_algorithm():
    a, b = 1.0000001, 1.0000002
    rel_tol = 1e-9
    abs_tol = 0.0
    
    diff = abs(a - b)
    relative_tolerance = rel_tol * max(abs(a), abs(b))
    threshold = max(relative_tolerance, abs_tol)
    
    print(f"Difference: {diff}")
    print(f"Threshold: {threshold}")
    print(f"Close enough: {diff <= threshold}")
    print(f"math.isclose(): {math.isclose(a, b)}")

demonstrate_algorithm()

python code snippet end

Practical Testing Scenarios

python code snippet start

import math

# Testing computational results
def test_square_root():
    x = 2.0
    result = math.sqrt(x) ** 2
    expected = 2.0
    
    # This might fail due to floating-point precision
    assert result == expected  # Risky!
    
    # This is robust
    assert math.isclose(result, expected)  # Safe!

# Iterative algorithm convergence
def check_convergence(current, previous, tolerance=1e-6):
    return math.isclose(current, previous, rel_tol=tolerance)

# Different tolerance strategies
values = [1.000001, 1.000002]
print(f"Default: {math.isclose(*values)}")                    # False
print(f"Loose: {math.isclose(*values, rel_tol=1e-5)}")       # True
print(f"With abs_tol: {math.isclose(*values, abs_tol=1e-5)}")  # True

python code snippet end

Working with Different Numeric Types

python code snippet start

from decimal import Decimal
from fractions import Fraction

# Works across numeric types
print(math.isclose(1.0, Decimal('1.0')))     # True
print(math.isclose(0.5, Fraction(1, 2)))     # True

# Handles edge cases gracefully
print(math.isclose(float('inf'), float('inf')))  # True
print(math.isclose(float('nan'), float('nan')))  # False (by design)

python code snippet end

Design Philosophy

The symmetric comparison method ensures that isclose(a, b) always equals isclose(b, a), making it predictable and mathematically sound. The function handles IEEE 754 special values (infinity, NaN) according to established standards.

PEP 485 solved a fundamental pain point in numerical computing by providing a standard, well-designed solution that works consistently across different scenarios and numeric types.

math module functions precise decimal arithmetic

Reference: PEP 485 – A Function for testing approximate equality