NumPy - np.allclose() - Compare with Tolerance
• `np.allclose()` compares arrays element-wise within absolute and relative tolerance thresholds, solving floating-point precision issues that break exact equality checks
Key Insights
• np.allclose() compares arrays element-wise within absolute and relative tolerance thresholds, solving floating-point precision issues that break exact equality checks
• The function uses the formula absolute(a - b) <= (atol + rtol * absolute(b)) for each element pair, where default values are rtol=1e-05 and atol=1e-08
• Understanding when to adjust tolerance parameters prevents false negatives in numerical computations, scientific calculations, and testing scenarios where minor differences are acceptable
Why Exact Equality Fails for Floating-Point Numbers
Floating-point arithmetic introduces rounding errors that make exact equality comparisons unreliable. Operations that should theoretically produce identical results often differ by tiny amounts.
import numpy as np
# Mathematical identity that fails with floating-point
a = 0.1 + 0.2
b = 0.3
print(f"a = {a}")
print(f"b = {b}")
print(f"a == b: {a == b}") # False
print(f"Difference: {a - b}") # 5.551115123125783e-17
# Array comparison with accumulated errors
arr1 = np.array([0.1 + 0.2, 0.4 + 0.5, 0.7 + 0.8])
arr2 = np.array([0.3, 0.9, 1.5])
print(f"\nDirect equality: {np.array_equal(arr1, arr2)}") # False
print(f"Using allclose: {np.allclose(arr1, arr2)}") # True
This demonstrates why np.allclose() is essential for real-world numerical comparisons where mathematical equivalence matters more than bit-level identity.
Basic Usage and Default Behavior
The np.allclose() function accepts two arrays and returns a single boolean indicating whether all elements are within tolerance.
import numpy as np
# Simple comparison with default tolerances
x = np.array([1.0, 2.0, 3.0])
y = np.array([1.0000001, 2.0000001, 3.0000001])
print(np.allclose(x, y)) # True
# Larger differences exceed default tolerance
z = np.array([1.001, 2.001, 3.001])
print(np.allclose(x, z)) # False
# Works with different shapes through broadcasting
scalar = 5.0
array = np.array([5.0, 5.0, 5.0])
print(np.allclose(scalar, array)) # True
# Multi-dimensional arrays
matrix1 = np.array([[1.0, 2.0], [3.0, 4.0]])
matrix2 = np.array([[1.0000001, 2.0000001], [3.0000001, 4.0000001]])
print(np.allclose(matrix1, matrix2)) # True
The default tolerance works well for most general-purpose comparisons but requires adjustment for specific use cases.
Understanding Relative and Absolute Tolerance
The tolerance formula combines both relative and absolute components: absolute(a - b) <= (atol + rtol * absolute(b)). This dual approach handles both small and large numbers appropriately.
import numpy as np
# Demonstrating relative tolerance (rtol)
# For large numbers, relative difference matters
large1 = np.array([1000000.0])
large2 = np.array([1000000.1])
print(f"Difference: {large2[0] - large1[0]}") # 0.1
print(f"Default rtol (1e-05): {np.allclose(large1, large2)}") # True
print(f"Stricter rtol (1e-08): {np.allclose(large1, large2, rtol=1e-08)}") # False
# Demonstrating absolute tolerance (atol)
# For small numbers near zero, absolute difference matters
small1 = np.array([1e-10])
small2 = np.array([2e-10])
print(f"\nDifference: {small2[0] - small1[0]}") # 1e-10
print(f"Default atol (1e-08): {np.allclose(small1, small2)}") # True
print(f"Stricter atol (1e-11): {np.allclose(small1, small2, atol=1e-11)}") # False
# Combined effect
mixed = np.array([1e-9, 1000.0])
reference = np.array([2e-9, 1000.001])
print(f"\nWith defaults: {np.allclose(mixed, reference)}") # True
print(f"Only rtol=1e-05: {np.allclose(mixed, reference, atol=0)}") # False (fails for small values)
print(f"Only atol=1e-08: {np.allclose(mixed, reference, rtol=0)}") # False (fails for large values)
Practical Applications in Testing
Unit tests for numerical algorithms require tolerance-based comparisons to avoid brittle tests that fail due to implementation details rather than actual errors.
import numpy as np
def matrix_multiply_custom(A, B):
"""Custom matrix multiplication implementation."""
return np.dot(A, B)
def test_matrix_operations():
"""Test suite using allclose for numerical stability."""
# Test 1: Identity matrix property
A = np.random.rand(3, 3)
I = np.eye(3)
result = matrix_multiply_custom(A, I)
assert np.allclose(result, A), "Identity matrix test failed"
# Test 2: Matrix inverse
B = np.random.rand(4, 4)
B_inv = np.linalg.inv(B)
product = matrix_multiply_custom(B, B_inv)
expected_identity = np.eye(4)
assert np.allclose(product, expected_identity), "Inverse test failed"
# Test 3: Transpose property
C = np.random.rand(2, 3)
D = np.random.rand(3, 2)
result1 = matrix_multiply_custom(C, D).T
result2 = matrix_multiply_custom(D.T, C.T)
assert np.allclose(result1, result2), "Transpose property test failed"
print("All tests passed!")
test_matrix_operations()
Handling Special Values
The function handles NaN and infinity values according to the equal_nan parameter, which is crucial for datasets with missing or undefined values.
import numpy as np
# NaN handling
arr_with_nan1 = np.array([1.0, 2.0, np.nan])
arr_with_nan2 = np.array([1.0, 2.0, np.nan])
print(f"Default (equal_nan=False): {np.allclose(arr_with_nan1, arr_with_nan2)}") # False
print(f"With equal_nan=True: {np.allclose(arr_with_nan1, arr_with_nan2, equal_nan=True)}") # True
# Infinity handling
arr_inf1 = np.array([1.0, np.inf, -np.inf])
arr_inf2 = np.array([1.0, np.inf, -np.inf])
print(f"\nInfinity comparison: {np.allclose(arr_inf1, arr_inf2)}") # True
# Mixed special values
mixed1 = np.array([0.0, np.inf, np.nan, -np.inf])
mixed2 = np.array([1e-10, np.inf, np.nan, -np.inf])
print(f"Mixed without equal_nan: {np.allclose(mixed1, mixed2)}") # False
print(f"Mixed with equal_nan: {np.allclose(mixed1, mixed2, equal_nan=True)}") # True
Domain-Specific Tolerance Configuration
Different domains require different tolerance levels. Scientific computing, financial calculations, and graphics processing each have distinct precision requirements.
import numpy as np
# Scientific computing: moderate tolerance
def compare_physical_measurements(measured, expected):
"""Compare experimental results with 0.1% tolerance."""
return np.allclose(measured, expected, rtol=1e-3, atol=1e-6)
measured_force = np.array([9.81, 19.62, 29.43])
expected_force = np.array([9.80, 19.60, 29.40])
print(f"Physical measurements match: {compare_physical_measurements(measured_force, expected_force)}")
# Financial calculations: strict tolerance
def compare_financial_values(calculated, expected):
"""Compare monetary values with cent precision."""
return np.allclose(calculated, expected, rtol=0, atol=0.01)
calculated_prices = np.array([99.991, 199.995, 299.989])
expected_prices = np.array([100.00, 200.00, 300.00])
print(f"Financial values match: {compare_financial_values(calculated_prices, expected_prices)}")
# Graphics/rendering: loose tolerance
def compare_pixel_values(rendered, reference):
"""Compare rendered output with 1% tolerance."""
return np.allclose(rendered, reference, rtol=1e-2, atol=1.0)
rendered_rgb = np.array([[255, 128, 64], [200, 150, 100]])
reference_rgb = np.array([[254, 127, 65], [201, 151, 99]])
print(f"Pixel values match: {compare_pixel_values(rendered_rgb, reference_rgb)}")
Performance Considerations
When comparing large arrays repeatedly, understanding np.allclose() performance characteristics helps optimize computational workflows.
import numpy as np
import time
# Performance comparison for large arrays
size = 10_000_000
large_array1 = np.random.rand(size)
large_array2 = large_array1 + np.random.rand(size) * 1e-6
# Timing allclose
start = time.time()
result = np.allclose(large_array1, large_array2)
allclose_time = time.time() - start
print(f"allclose time: {allclose_time:.4f}s, result: {result}")
# Manual tolerance check (less efficient)
start = time.time()
diff = np.abs(large_array1 - large_array2)
manual_result = np.all(diff <= 1e-5 * np.abs(large_array2) + 1e-8)
manual_time = time.time() - start
print(f"Manual check time: {manual_time:.4f}s, result: {manual_result}")
# Early termination benefit
almost_identical = large_array1.copy()
almost_identical[0] = 999.0 # Single large difference
start = time.time()
result_early = np.allclose(almost_identical, large_array1)
early_time = time.time() - start
print(f"Early termination time: {early_time:.4f}s, result: {result_early}")
The np.allclose() function provides optimized C-level implementation that outperforms manual Python loops while offering the flexibility needed for robust numerical comparisons across diverse application domains.