Python math.frexp(): Decomposing Floats into Mantissa and Exponent

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.

Syntax & Parameters 🔝

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).

Basic Usage & Float Decomposition 🔝

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

Mantissa Normalization Range: [0.5, 1.0) 🔝

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):

  • Internal IEEE 754 Hardware Format: Numbers are represented with an implicit leading 1 bit: 1.fraction × 2exp. In this scheme, the significand lies in [1.0, 2.0).
  • C & Python 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

Special Values: Zero, Signed Zero, Inf, and NaN 🔝

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)

math.frexp() vs. math.ldexp() vs. math.modf() 🔝

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

Hands-on Exercise: Floating-Point ULP (Machine Precision) Calculator with Assertions 🔝

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):

ULP(x) = 2e − 53 = math.ldexp(1.0, e − 53)

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:

Python Exercise: Precision ULP Calculator via math.frexp()
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!")

Practice in Google Colab & GitHub 🔝

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.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer