The math.expm1(x) function computes ex - 1 (Euler's constant e ≈ 2.71828 raised to the power x, minus one). It is specifically engineered to prevent catastrophic cancellation — the severe loss of significant digits that occurs in standard IEEE 754 floating-point arithmetic when subtracting 1.0 from math.exp(x) for values of x near zero.
Mathematical Symmetry: math.expm1(x) is the exact mathematical inverse of math.log1p(y). For small quantities, the round-trip identity math.log1p(math.expm1(x)) == x preserves precision where naive formulas collapse.
The math.expm1() function is part of the standard Python math library and operates on numeric inputs:
import math
result = math.expm1(x)
| Parameter | Type | Description |
|---|---|---|
x |
float or int |
Required. The numeric exponent. Can be positive, negative, or zero. |
| Return Value | float |
Returns ex - 1 as an IEEE 754 double-precision float. |
OverflowError |
Exception | Raised when x > ~709.7827 (where ex exceeds float64 maximum ≈ 1.797e+308). |
TypeError |
Exception | Raised when x is non-numeric (e.g. a string or list). |
When x is close to zero, math.exp(x) produces a number very close to 1.0. Because IEEE 754 64-bit floats have only 53 bits of mantissa (about 15 to 17 decimal digits of precision), adding small x to 1.0 drops the lowest bits of x. When you then subtract 1.0, those lost bits cannot be recovered:
import math
# Moderate input: both produce similar outputs
print("x = 1.0:")
print(" math.exp(1.0) - 1:", math.exp(1.0) - 1)
print(" math.expm1(1.0): ", math.expm1(1.0))
# Very small input: 1e-15
x = 1e-15
print("\nx = 1e-15:")
print(" math.exp(x) - 1: ", math.exp(x) - 1)
print(" math.expm1(x): ", math.expm1(x))
# Ultra-small input: 1e-17 (falls below float64 epsilon)
x_tiny = 1e-17
print("\nx = 1e-17:")
print(" math.exp(x) - 1: ", math.exp(x_tiny) - 1) # Complete loss -> 0.0
print(" math.expm1(x): ", math.expm1(x_tiny)) # Exact -> 1e-17
Output:
x = 1.0:
math.exp(1.0) - 1: 1.718281828459045
math.expm1(1.0): 1.718281828459045
x = 1e-15:
math.exp(x) - 1: 1.1102230246251565e-15
math.expm1(x): 1.0000000000000005e-15
x = 1e-17:
math.exp(x) - 1: 0.0
math.expm1(x): 1e-17
x = 1e-15, math.exp(x) - 1 has an error of more than 11% due to mantissa truncation. At x = 1e-17, math.exp(x) - 1 collapses entirely to 0.0 (100% relative error). In contrast, math.expm1(x) accurately preserves the correct magnitude.
The table below highlights how relative error escalates when using naive math.exp(x) - 1 compared to the numerically stable math.expm1(x) across decreasing orders of magnitude:
Exponent (x) |
Naive math.exp(x) - 1 |
Accurate math.expm1(x) |
Relative Error | Severity Status |
|---|---|---|---|---|
1.0 |
1.718281828459045 |
1.718281828459045 |
0.0% |
Exact |
1e-5 |
1.0000050000069649e-05 |
1.0000050000166668e-05 |
~1e-11% |
Negligible |
1e-10 |
1.000000082740371e-10 |
1.00000000005e-10 |
~0.00008% |
Mild Drift |
1e-15 |
1.1102230246251565e-15 |
1.0000000000000005e-15 |
~11.02% |
Severe Loss |
1e-17 |
0.0 |
1.0000000000000000e-17 |
100.0% |
Total Wipeout |
The standard Maclaurin (Taylor) series expansion for the exponential function ex is:
ex = 1 + x + x2/2! + x3/3! + x4/4! + …
When computing ex - 1, the initial constant 1 cancels analytically:
ex - 1 = x + x2/2! + x3/3! + x4/4! + … = x · (1 + x/2 + x2/6 + …)
By factoring out x and computing the polynomial directly without ever adding 1.0, the underlying C mathematical library evaluates small arguments without cancellation error.
import math
def taylor_expm1(x, terms=5):
# Direct summation: x + x^2/2 + x^3/6 + ...
total = 0.0
term = x
for n in range(1, terms + 1):
total += term
term *= x / (n + 1)
return total
x = 1e-8
print("Taylor approx:", taylor_expm1(x))
print("math.expm1: ", math.expm1(x))
print("math.exp - 1: ", math.exp(x) - 1)
Output:
Taylor approx: 1.000000005e-08
math.expm1: 1.000000005e-08
math.exp - 1: 1.000000082740371e-08
Just as math.log1p(y) computes ln(1 + y) accurately for small y, math.expm1(x) is its exact inverse function:
| Forward Operation | Inverse Operation | Round-Trip Identity |
|---|---|---|
y = math.expm1(x) |
x = math.log1p(y) |
math.log1p(math.expm1(x)) == x |
import math
test_values = [1e-12, 1e-8, 0.05, 1.5, -0.4]
print(f"{'Input x':>10} | {'expm1(x)':>15} | {'log1p(expm1(x))':>18} | {'Relative Error':>15}")
print("-" * 65)
for val in test_values:
forward = math.expm1(val)
reconstructed = math.log1p(forward)
rel_error = abs(reconstructed - val) / abs(val)
print(f"{val:>10.2e} | {forward:>15.8e} | {reconstructed:>18.8e} | {rel_error:>15.2e}")
Output:
Input x | expm1(x) | log1p(expm1(x)) | Relative Error
-----------------------------------------------------------------
1.00e-12 | 1.00000000e-12 | 1.00000000e-12 | 0.00e+00
1.00e-08 | 1.00000001e-08 | 1.00000000e-08 | 0.00e+00
5.00e-02 | 5.12710964e-02 | 5.00000000e-02 | 0.00e+00
1.50e+00 | 3.48168907e+00 | 1.50000000e+00 | 0.00e+00
-4.00e-01 | -3.29679954e-01 | -4.00000000e-01 | 0.00e+00
Understanding the asymptotic limits of math.expm1() is essential for robust numerical code:
math.expm1(0.0) == 0.0 and math.expm1(-0.0) == -0.0.x → -∞, ex → 0, so math.expm1(x) smoothly approaches -1.0.math.log(sys.float_info.max) ≈ 709.7827. Exponents above this value raise OverflowError.import math
# 1. Asymptote as x becomes deeply negative
print("expm1(-10): ", math.expm1(-10))
print("expm1(-50): ", math.expm1(-50))
print("expm1(-1000):", math.expm1(-1000)) # Approached -1.0 exactly
# 2. Handling Overflow boundary
try:
overflow_val = math.expm1(710)
except OverflowError as e:
print("Overflow caught:", e)
Output:
expm1(-10): -0.9999546000702375
expm1(-50): -1.0
expm1(-1000): -1.0
Overflow caught: math range error
In quantitative finance, continuous compounding yield over a short interval (e.g. overnight lending, intraday rates) is given by Accrued = Principal · (er·t - 1). Using math.expm1(r * t) prevents fractional cent inaccuracies:
import math
def continuous_accrued_interest(principal, annual_rate, hours):
t_years = hours / (365.25 * 24)
return principal * math.expm1(annual_rate * t_years)
principal = 10_000_000.0 # $10 Million
rate = 0.0525 # 5.25%
interest_2hr = continuous_accrued_interest(principal, rate, hours=2)
print(f"2-Hour Interest on $10M: ${interest_2hr:.4f}")
Output:
2-Hour Interest on $10M: $119.7816
The fraction of a radioactive isotope that decays during time t is 1 - e-λt = -expm1(-λt). For short observation windows compared to the half-life, this formulation preserves precision:
import math
def decayed_fraction(half_life_years, elapsed_days):
decay_constant = math.log(2) / half_life_years
t_years = elapsed_days / 365.25
return -math.expm1(-decay_constant * t_years)
# Carbon-14 (half-life = 5,730 years), observation: 1 day
frac = decayed_fraction(5730, elapsed_days=1)
print(f"Decayed fraction in 1 day: {frac:.6e}")
Output:
Decayed fraction in 1 day: 3.311956e-07
In logistic regression and statistical epidemiology, regression coefficients β represent log-odds ratios. The percentage change in odds is given by eβ - 1 = expm1(β):
import math
# Small feature weight in logistic regression model
beta_weight = 0.0035
pct_change = math.expm1(beta_weight) * 100
print(f"Relative risk change: +{pct_change:.4f}%")
Output:
Relative risk change: +0.3506%
The following production script implements high-precision financial yield calculation and physical decay tracking with self-checking assertions:
import math
def effective_continuous_rate(nominal_rate, duration_seconds):
# Calculates exact effective rate over tiny duration intervals
if duration_seconds < 0 or nominal_rate < 0:
raise ValueError("Rate and duration must be non-negative.")
t_years = duration_seconds / (365.25 * 86400)
exponent = nominal_rate * t_years
return math.expm1(exponent)
def safe_inverse_rate(effective_rate, duration_seconds):
# Reconstructs nominal annual rate from effective rate using log1p
t_years = duration_seconds / (365.25 * 86400)
return math.log1p(effective_rate) / t_years
# --- Unit Assertions and Verification ---
# 1. Zero duration yields zero effective rate
assert effective_continuous_rate(0.05, 0) == 0.0
# 2. 1-second interval at 5% rate
rate_1s = effective_continuous_rate(0.05, 1)
assert rate_1s > 0.0
assert abs(safe_inverse_rate(rate_1s, 1) - 0.05) < 1e-14
# 3. Verify accuracy advantage over naive exp() - 1
dt_tiny = 1e-8
naive_val = math.exp(dt_tiny) - 1.0
expm1_val = math.expm1(dt_tiny)
assert abs(expm1_val - dt_tiny) < 1e-15
# 4. Asymptotic check for negative numbers
assert math.expm1(-500) == -1.0
print("Effective rate for 10 seconds at 6%:", effective_continuous_rate(0.06, 10))
print("All precision expm1() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all precision tests interactively, compare catastrophic cancellation benchmarks, evaluate inverse duality with log1p, and simulate financial micro-interval rates in Google Colab.
ex.ln(1 + x) for small values.Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.