The math.log1p(x) function computes the natural logarithm of 1 + x (&ln;(1 + x)) using base e. Crucially, math.log1p() is designed to prevent catastrophic cancellation: when |x| is extremely small (< 10−14), evaluating naive math.log(1 + x) causes 1 + x to round to 1.0, resulting in complete precision loss (0.0), whereas math.log1p(x) evaluates the series directly to retain full 53-bit float precision.
Valid Domain: Defined for all real numbers strictly greater than negative one (x > −1.0). Passing x ≤ −1.0 raises a ValueError: math domain error.
The math.log1p() function is part of Python's standard math module:
import math
result = math.log1p(x)
| Parameter | Type | Description |
|---|---|---|
x |
float or int |
Required. A real number > −1.0. |
| Return Value | float |
The natural logarithm &ln;(1 + x) as an IEEE 754 float. |
ValueError |
Exception | Raised when x ≤ −1.0 (e.g. math.log1p(-1.0) or math.log1p(-2.0)). |
TypeError |
Exception | Raised when passing non-numeric types (e.g. strings, lists, None). |
A common misconception is that log1p(x) requires positive numbers. In reality, log1p(x) is defined for all real numbers greater than −1.0 (i.e. 1 + x > 0):
import math
# Origin value: ln(1 + 0) == ln(1) == 0.0
print("log1p(0.0): ", math.log1p(0.0)) # Output: 0.0
# Positive values: log1p(1.0) == ln(2) == 0.693147
print("log1p(1.0): ", round(math.log1p(1.0), 6)) # Output: 0.693147
print("log1p(9.0): ", round(math.log1p(9.0), 6)) # ln(10) Output: 2.302585
# Negative values in (-1.0, 0.0) are fully valid
print("log1p(-0.5):", round(math.log1p(-0.5), 6)) # ln(0.5) Output: -0.693147
print("log1p(-0.1):", round(math.log1p(-0.1), 6)) # ln(0.9) Output: -0.105361
Output:
log1p(0.0): 0.0
log1p(1.0): 0.693147
log1p(9.0): 2.302585
log1p(-0.5): -0.693147
log1p(-0.1): -0.105361
Values ≤ −1.0 trigger domain exceptions:
for bad in [-1.0, -2.5]:
try:
math.log1p(bad)
except ValueError as err:
print(f"log1p({bad}): ValueError -> {err}")
Output:
log1p(-1.0): ValueError -> math domain error
log1p(-2.5): ValueError -> math domain error
In standard 64-bit IEEE 754 floating-point arithmetic, floats maintain approximately 15 to 17 decimal digits of precision (53 significand bits). When you evaluate 1 + x with a very small x (such as 1e-15 or 1e-16), the low-order bits of x are discarded because they lie beyond the 53-bit register:
import math
small_x = 1e-16
# 1. Look what happens to 1 + x
print("1 + 1e-16 == 1.0? ", (1.0 + small_x) == 1.0) # True! (Precision wiped out)
# 2. Evaluating naive math.log(1 + x)
print("math.log(1.0 + 1e-16): ", math.log(1.0 + small_x)) # 0.0 (100% data loss!)
# 3. Evaluating math.log1p(x)
print("math.log1p(1e-16): ", math.log1p(small_x)) # 1e-16 (Exact!)
Output:
1 + 1e-16 == 1.0? True
math.log(1.0 + 1e-16): 0.0
math.log1p(1e-16): 1e-16
math.log1p() achieve this? Instead of adding x to 1, math.log1p(x) employs specialized series expansions (such as the Mercator series: ln(1 + x) = x - x²/2 + x³/3 - ...). For tiny x, ln(1 + x) ≈ x, preserving every bit of significant precision.
Just as math.log() is the inverse of math.exp(), math.log1p() is the exact mathematical inverse of math.expm1() (which calculates ex − 1 for small numbers without precision loss):
import math
test_vals = [1e-15, 1e-8, 0.05, 2.0]
for v in test_vals:
round_trip = math.expm1(math.log1p(v))
print(f"Original: {v:.1e} -> expm1(log1p): {round_trip:.1e} | Match: {math.isclose(v, round_trip)}")
Output:
Original: 1.0e-15 -> expm1(log1p): 1.0e-15 | Match: True
Original: 1.0e-08 -> expm1(log1p): 1.0e-08 | Match: True
Original: 5.0e-02 -> expm1(log1p): 5.0e-02 | Match: True
Original: 2.0e+00 -> expm1(log1p): 2.0e+00 | Match: True
In machine learning and statistics, feature distributions (such as website visits, revenue, or word counts) are frequently right-skewed with a large spike of zero values. Standard log transformations fail because log(0) produces −inf:
import math
raw_pageviews = [0, 1, 5, 25, 1000, 50000]
# Log1p transforms 0 cleanly to 0.0 without invalid math errors
transformed = [round(math.log1p(x), 3) for x in raw_pageviews]
print("Raw pageviews: ", raw_pageviews)
print("Log1p normalized:", transformed)
Output:
Raw pageviews: [0, 1, 5, 25, 1000, 50000]
Log1p normalized: [0.0, 0.693, 1.792, 3.258, 6.909, 10.82]
In financial modeling and algorithmic trading, stock prices change daily by tiny fractions (e.g. +0.042% or −0.015%). While simple percentage returns are R = (Pt − Pt−1) / Pt−1, continuous compounded portfolio analysis requires log returns:
To compute total cumulative portfolio growth from a series of daily log returns r1, r2, ..., rn, sum the log returns and apply expm1: Rtotal = expm1(∑ ri). Let's build an algorithmic log-return tracker verified with assertions:
import math
def simple_to_log_return(simple_return):
"""Converts simple decimal return R into continuous log return r = log1p(R)."""
if simple_return <= -1.0:
raise ValueError("Total loss (-100% or worse) has undefined log return.")
return math.log1p(simple_return)
def cumulative_growth(daily_simple_returns):
"""Computes total cumulative return from daily simple returns via log1p and expm1."""
log_returns = [simple_to_log_return(r) for r in daily_simple_returns]
total_log_return = math.fsum(log_returns)
return math.expm1(total_log_return)
# Unit Assertions
# 1. Tiny return of 1e-15 evaluates accurately without cancelling to 0.0
tiny_r = 1e-15
assert simple_to_log_return(tiny_r) == tiny_r
assert math.log(1.0 + tiny_r) != tiny_r # Naive log fails!
# 2. Zero return yields exactly 0.0
assert simple_to_log_return(0.0) == 0.0
# 3. Two compounding days (+10% and -10%):
# Total simple return should be: (1.10 * 0.90) - 1.0 = -0.01 (-1.0%)
cum_return = cumulative_growth([0.10, -0.10])
assert math.isclose(cum_return, -0.01)
# 4. Out of bounds validation (<= -1.0)
try:
simple_to_log_return(-1.0)
assert False, "Should raise ValueError"
except ValueError:
pass
sample_days = [0.005, -0.002, 0.008, -0.001]
print(f"Cumulative return over 4 trading days: {cumulative_growth(sample_days)*100:.3f}%")
print("All financial log-return assertions passed successfully!")
Open in Google Colab View on GitHub
Run all math.log1p() examples interactively, benchmark catastrophic cancellation against math.log(1 + x), test data normalization, and execute financial log-return calculations in Google Colab.
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.