The math.frexp(x) function decomposes a real numeric value x into a 2-element tuple (m, e), where m is a floating-point mantissa (significand) and e is an integer exponent of 2 such that x == m × 2e. The mantissa is normalized such that 0.5 ≤ |m| < 1.0 for non-zero inputs, with math.frexp(0.0) returning (0.0, 0).
Exact Inverse: math.frexp() is the exact complement of math.ldexp(). Rebuilding the number with math.ldexp(m, e) reproduces the original float with zero loss of precision.
The math.frexp() function is part of Python's standard math module and maps directly to the standard C library frexp() function:
import math
m, e = math.frexp(x)
| Component | Type | Description |
|---|---|---|
x |
float or int |
Required. The numeric value to decompose into mantissa and exponent. |
m (Mantissa) |
float |
Normalized significand satisfying 0.5 ≤ |m| < 1.0 (or 0.0 when x == 0.0). |
e (Exponent) |
int |
Integer power of 2 such that x == m × 2e. |
| Return Value | tuple |
A 2-element tuple (float, int) representing (m, e). |
TypeError |
Exception | Raised when passing non-numeric data types (such as strings or dictionaries). |
When decomposing numbers, notice how powers of two always yield a mantissa of exactly 0.5, while arbitrary floats produce fractional mantissas within [0.5, 1.0):
import math
# Powers of 2: 1 = 0.5 * 2^1, 4 = 0.5 * 2^3, 8 = 0.5 * 2^4
print("frexp(1.0): ", math.frexp(1.0)) # (0.5, 1)
print("frexp(4.0): ", math.frexp(4.0)) # (0.5, 3)
print("frexp(8.0): ", math.frexp(8.0)) # (0.5, 4)
# Fractional numbers < 1.0: 0.125 = 0.5 * 2^(-2)
print("frexp(0.125):", math.frexp(0.125)) # (0.5, -2)
# Arbitrary non-power-of-2 numbers: 10.0 = 0.625 * 2^4
print("frexp(10.0): ", math.frexp(10.0)) # (0.625, 4)
# Negative numbers inherit the sign in the mantissa
print("frexp(-5.0): ", math.frexp(-5.0)) # (-0.625, 3)
Output:
frexp(1.0): (0.5, 1)
frexp(4.0): (0.5, 3)
frexp(8.0): (0.5, 4)
frexp(0.125): (0.5, -2)
frexp(10.0): (0.625, 4)
frexp(-5.0): (-0.625, 3)
We can unpack the returned tuple directly into descriptive variables and verify the equality equation x == m * (2 ** e):
val = 13.375
mantissa, exponent = math.frexp(val)
print(f"Value: {val}")
print(f"Mantissa: {mantissa} (lies in [0.5, 1.0))")
print(f"Exponent: {exponent}")
print(f"Verification: {mantissa} * (2 ** {exponent}) == {mantissa * (2 ** exponent)}")
Output:
Value: 13.375
Mantissa: 0.8359375 (lies in [0.5, 1.0))
Exponent: 4
Verification: 0.8359375 * (2 ** 4) == 13.375
A common point of confusion among engineers is why math.frexp() normalizes mantissas to [0.5, 1.0) rather than the IEEE 754 internal standard interval [1.0, 2.0):
1.fraction × 2exp. In this scheme, the significand lies in [1.0, 2.0).frexp() Convention: Defined by ANSI/ISO C standard library <math.h>, the mantissa is shifted by one bit so that it always represents a pure fraction strictly less than 1.0: 0.5 ≤ |m| < 1.0.Because m is constrained to [0.5, 1.0) for positive numbers, the integer exponent e is related directly to the base-2 logarithm: for any positive number x > 0, e == math.floor(math.log2(x)) + 1:
import math
numbers = [0.25, 0.7, 1.0, 7.5, 1024.0]
print(f"{'x':>8} | {'frexp exponent':>15} | {'floor(log2(x)) + 1':>18} | Match?")
print("-" * 55)
for n in numbers:
_, e = math.frexp(n)
expected_e = math.floor(math.log2(n)) + 1
print(f"{n:8.2f} | {e:15d} | {expected_e:18d} | {e == expected_e}")
Output:
x | frexp exponent | floor(log2(x)) + 1 | Match?
-------------------------------------------------------
0.25 | -1 | -1 | True
0.70 | 0 | 0 | True
1.00 | 1 | 1 | True
7.50 | 3 | 3 | True
1024.00 | 11 | 11 | True
Python's math.frexp() defines clean, standardized behavior for boundary edge cases:
Input x |
Output (m, e) |
Behavioral Notes |
|---|---|---|
0.0 |
(0.0, 0) |
Zero cannot be normalized to [0.5, 1.0); returns 0 for both. |
-0.0 |
(-0.0, 0) |
Preserves the negative sign of zero (IEEE 754 signed zero). |
float('inf') |
(inf, 0) |
Returns infinity as mantissa with zero exponent. |
float('nan') |
(nan, 0) |
Returns NaN as mantissa with zero exponent. |
Subnormal float (e.g. 1e-315) |
(m, e) with e < -1022 |
Correctly decomposes denormalized/subnormal numbers. |
import math
print("frexp(0.0): ", math.frexp(0.0))
print("frexp(-0.0): ", math.frexp(-0.0))
print("frexp(inf): ", math.frexp(float('inf')))
print("frexp(nan): ", math.frexp(float('nan')))
Output:
frexp(0.0): (0.0, 0)
frexp(-0.0): (-0.0, 0)
frexp(inf): (inf, 0)
frexp(nan): (nan, 0)
Python provides three low-level floating-point decomposition and reconstruction functions. Choosing the right one depends on whether you are working in base 2 (binary exponents) or base 10 (decimal fractions):
| Function | Operation | Input | Output | Primary Use Case |
|---|---|---|---|---|
math.frexp(x) |
Decompose into binary mantissa & exponent | x (float) |
(m, e) where x = m × 2e |
IEEE 754 precision, bit-level serialization, ULP calculation. |
math.ldexp(m, e) |
Reconstruct float from mantissa & exponent | m (float), e (int) |
m × 2e (float) |
Inverse of frexp; fast power-of-two multiplication. |
math.modf(x) |
Separate fractional and integer parts | x (float) |
(fraction, integer) |
Decimal splitting, formatting numbers, clock/time conversions. |
Verifying the inverse relationship between frexp() and ldexp():
import math
original = 98765.4321
m, e = math.frexp(original)
reconstructed = math.ldexp(m, e)
print("Original: ", original)
print("Reconstructed:", reconstructed)
print("Exact Match? ", original == reconstructed) # True
In numerical analysis, ULP (Unit in the Last Place) represents the gap between a floating-point number and its nearest representable neighbor. Because IEEE 754 64-bit double-precision floats have 53 bits of precision (52 explicit fraction bits plus 1 implicit leading bit), the distance between adjacent floats at magnitude x is governed directly by the exponent e returned by frexp(x):
For example, at x = 1.0, frexp(1.0) returns exponent e = 1, giving ULP(1.0) = 2^(1 - 53) = 2^(-52) ≈ 2.220446 × 10-16 (standard machine epsilon). Let's build an ULP calculator and verify it with unit assertions:
import math
def calculate_ulp(x):
"""
Calculates the Unit in the Last Place (ULP) for a 64-bit IEEE 754 float x.
ULP represents the resolution/gap between x and the next distinct float.
"""
if not isinstance(x, (int, float)):
raise TypeError("Input must be a numeric value.")
if math.isnan(x) or math.isinf(x):
return float('nan')
if x == 0.0:
# Smallest subnormal float gap (2**-1074)
return math.ldexp(1.0, -1074)
_, e = math.frexp(x)
return math.ldexp(1.0, e - 53)
# Unit Assertions
eps = 2.220446049250313e-16 # 2**-52
assert math.isclose(calculate_ulp(1.0), eps)
assert math.isclose(calculate_ulp(2.0), eps * 2)
assert math.isclose(calculate_ulp(0.5), eps / 2)
assert math.isclose(calculate_ulp(-1.0), eps)
# Type verification
try:
calculate_ulp("invalid")
assert False, "Should raise TypeError"
except TypeError:
pass
print("ULP at x = 1.0 (Machine Epsilon):", calculate_ulp(1.0))
print("ULP at x = 1,000,000.0: ", calculate_ulp(1_000_000.0))
print("All ULP machine precision assertions passed successfully!")
Open in Google Colab View on GitHub
Run all math.frexp() examples interactively, inspect mantissa normalization intervals, verify round-trip reconstruction with math.ldexp(), and compute floating-point machine precision 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.