The math.ldexp(x, i) function computes x × 2i (multiplying a number by 2 raised to an integer power). It is the exact mathematical inverse of math.frexp(). Instead of performing expensive exponentiation or repeated multiplication, math.ldexp() directly manipulates the binary exponent of IEEE 754 floating-point numbers at the C standard library level, making it exceptionally fast and bit-exact.
Exact Inverse: If m, e = math.frexp(val), then math.ldexp(m, e) == val reconstructing the original float with zero loss of precision.
The math.ldexp() function belongs to Python's built-in math module:
import math
math.ldexp(x, i)
| Parameter | Type | Description |
|---|---|---|
x |
float or int |
Required. The multiplicand (significand / mantissa). Can be positive, negative, or zero. |
i |
int |
Required. The integer exponent of 2. Passing a float raises a TypeError. |
| Return Value | float |
The result of x × 2i as a 64-bit IEEE 754 floating-point number. |
TypeError |
Exception | Raised when i is a float (e.g. math.ldexp(2.0, 3.5)). |
OverflowError |
Exception | Raised when the calculated magnitude exceeds the maximum representable float (~1.7977 × 10308). |
Whether shifting values upwards with positive exponents or scaling down with negative exponents, math.ldexp() returns an exact float:
import math
# Multiplying by positive powers of 2 (Left shift scaling)
print("ldexp(3.0, 4): ", math.ldexp(3.0, 4)) # 3.0 * (2**4) = 3.0 * 16 = 48.0
print("ldexp(1.5, 3): ", math.ldexp(1.5, 3)) # 1.5 * 8 = 12.0
# Multiplying by negative powers of 2 (Division by powers of 2)
print("ldexp(10.0, -2):", math.ldexp(10.0, -2)) # 10.0 / 4 = 2.5
print("ldexp(1.0, -3): ", math.ldexp(1.0, -3)) # 1.0 / 8 = 0.125
# Exponent of 0 returns the input unchanged as a float
print("ldexp(7.25, 0): ", math.ldexp(7.25, 0)) # 7.25 * 1 = 7.25
# Preserving sign and negative zero
print("ldexp(-2.5, 2): ", math.ldexp(-2.5, 2)) # -2.5 * 4 = -10.0
print("ldexp(-0.0, 5): ", math.ldexp(-0.0, 5)) # Preserves signed -0.0
Output:
ldexp(3.0, 4): 48.0
ldexp(1.5, 3): 12.0
ldexp(10.0, -2): 2.5
ldexp(1.0, -3): 0.125
ldexp(7.25, 0): 7.25
ldexp(-2.5, 2): -10.0
ldexp(-0.0, 5): -0.0
The companion function math.frexp(x) decomposes any real number x into a fractional mantissa m and integer exponent e such that 0.5 ≤ |m| < 1.0 and x = m × 2e. math.ldexp(m, e) reconstructs the exact original value:
import math
test_values = [0.0, 0.125, 1.0, 13.375, 1024.0, -88.5, 1e15]
print(f"{'Original (x)':>14} | {'Mantissa (m)':>12} | {'Exp (e)':>7} | {'Reconstructed':>14} | Match?")
print("-" * 64)
for orig in test_values:
m, e = math.frexp(orig)
reconstructed = math.ldexp(m, e)
matches = (reconstructed == orig)
print(f"{orig:14.3f} | {m:12.6f} | {e:7d} | {reconstructed:14.3f} | {matches}")
Output:
Original (x) | Mantissa (m) | Exp (e) | Reconstructed | Match?
----------------------------------------------------------------
0.000 | 0.000000 | 0 | 0.000 | True
0.125 | 0.500000 | -2 | 0.125 | True
1.000 | 0.500000 | 1 | 1.000 | True
13.375 | 0.835938 | 4 | 13.375 | True
1024.000 | 0.500000 | 11 | 1024.000 | True
-88.500 | -0.691406 | 7 | -88.500 | True
1000000000000000.000 | 0.888178 | 50 | 1000000000000000.000 | True
Under the IEEE 754 double-precision standard, a 64-bit float comprises three fields:
When you execute x * (2 ** i) or x * math.pow(2, i), Python must compute the power of 2 (allocating a large integer or evaluating a floating-point power routine) and then perform a standard floating-point multiplication. In contrast, math.ldexp(x, i) delegates directly to the standard C library function ldexp(), which simply adds i directly to the float's 11-bit exponent register:
| Approach | How It Works | Rounding & Bit-Exactness |
|---|---|---|
math.ldexp(x, i) |
Adds i directly to the float exponent field |
Bit-exact. Zero intermediate rounding errors. |
x * (2 ** i) |
Calculates integer power of 2, then multiplies | Exact for small i, but allocates Python integers. |
x * math.pow(2, i) |
Evaluates exp(i * ln(2)), then multiplies |
Subject to potential floating-point rounding jitter. |
Because math.ldexp() operates strictly on binary exponent bitfields, it enforces strict type and range constraints:
Passing a floating-point number as the exponent i raises a TypeError:
import math
try:
math.ldexp(4.0, 2.5) # Exponent must be integer!
except TypeError as err:
print("TypeError caught:", err)
Output:
TypeError caught: 'float' object cannot be interpreted as an integer
The maximum exponent supported by IEEE 754 64-bit floats is +1023. If x × 2i exceeds the maximum floating-point value, Python raises an OverflowError:
import math
try:
math.ldexp(1.0, 1024)
except OverflowError as err:
print("OverflowError caught:", err)
Output:
OverflowError caught: math range error
Conversely, when i is extremely negative (e.g. i ≤ -1075), the result gracefully underflows past subnormal float precision and returns 0.0 without raising an error:
import math
print("ldexp(1.0, -1074):", math.ldexp(1.0, -1074)) # Smallest subnormal float: 5e-324
print("ldexp(1.0, -1075):", math.ldexp(1.0, -1075)) # Underflows to 0.0
In Digital Signal Processing (DSP) and audio engineering, 16-bit Pulse Code Modulation (PCM) audio samples are stored as signed 16-bit integers ranging from -32,768 to +32,767. To apply digital filters, audio algorithms require samples normalized to the floating-point range [-1.0, 1.0].
Because 16-bit audio uses a 15-bit amplitude magnitude (with 1 bit for sign), scaling to float is performed by multiplying by 2−15 via math.ldexp(sample, -15). Conversely, converting normalized float audio back to 16-bit integer PCM is achieved by scaling by 215 via math.ldexp(val, 15):
import math
def pcm16_to_float(pcm_val):
"""Converts 16-bit signed integer PCM (-32768 to 32767) to normalized float [-1.0, 1.0]."""
if not isinstance(pcm_val, int):
raise TypeError("PCM sample must be an integer.")
if pcm_val < -32768 or pcm_val > 32767:
raise ValueError("PCM sample out of 16-bit signed range [-32768, 32767].")
return round(math.ldexp(pcm_val, -15), 6)
def float_to_pcm16(float_val):
"""Converts normalized float audio [-1.0, 1.0] back to 16-bit signed integer PCM."""
if not isinstance(float_val, (int, float)):
raise TypeError("Normalized sample must be numeric.")
clamped = max(-1.0, min(1.0, float(float_val)))
scaled = math.ldexp(clamped, 15)
return int(round(scaled))
# Unit Assertions
assert pcm16_to_float(0) == 0.0
assert pcm16_to_float(32767) == 0.999969
assert pcm16_to_float(-32768) == -1.0
assert pcm16_to_float(16384) == 0.5
assert float_to_pcm16(0.0) == 0
assert float_to_pcm16(0.5) == 16384
assert float_to_pcm16(-1.0) == -32768
# Boundary and type checking assertions
try:
pcm16_to_float(40000)
assert False, "Should raise ValueError"
except ValueError:
pass
raw_audio = [-32768, -16384, 0, 16384, 32767]
normalized = [pcm16_to_float(s) for s in raw_audio]
print("Raw 16-bit PCM: ", raw_audio)
print("Normalized float:", normalized)
print("All DSP audio conversion assertions passed successfully!")
Open in Google Colab View on GitHub
Run all math.ldexp() scaling examples, test round-trip reconstruction with math.frexp(), inspect floating-point boundary behavior, and execute DSP fixed-point audio conversions 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.