The math.modf(x) function separates a real number x into its fractional and integer components, returning both as a 2-element tuple of floats: (fractional_part, integer_part). Crucially, both parts inherit the mathematical sign of x, and the integer component is returned as a float (e.g. 23.0, not 23).
Exact Component Order: Notice that the fractional part comes first (result[0]) and the integer part comes second (result[1]), satisfying the exact relation result[0] + result[1] == x.
The math.modf() function belongs to Python's standard math module and wraps the C standard library modf() function directly:
import math
fraction, integer = math.modf(x)
| Component | Type | Description |
|---|---|---|
x |
float or int |
Required. The numeric value to split into parts. |
fractional_part |
float |
The fractional portion of x. Carries the sign of x, with magnitude in [0.0, 1.0). |
integer_part |
float |
The integral portion of x (truncated towards zero). Carries the sign of x. |
| Return Value | tuple |
A 2-element tuple of floats: (float, float). |
TypeError |
Exception | Raised when passing non-numeric data types (strings, lists, None). |
You can inspect the raw returned tuple or unpack it immediately into two separate variables:
import math
# Standard floating-point number
parts = math.modf(23.45)
print("modf(23.45): ", parts)
print("Fractional (index 0):", parts[0])
print("Integer (index 1):", parts[1])
# Pure integer inputs return 0.0 fractional part
print("modf(42): ", math.modf(42))
# Mathematical constants like pi
print("modf(math.pi): ", math.modf(math.pi))
Output:
modf(23.45): (0.4499999999999993, 23.0)
Fractional (index 0): 0.4499999999999993
Integer (index 1): 23.0
modf(42): (0.0, 42.0)
modf(math.pi): (0.14159265358979312, 3.0)
Because the return value is a standard Python tuple, the idiomatic way to handle the output is unpacking:
frac, whole = math.modf(123.75)
print(f"Whole dollars: {int(whole)}, Cents portion: {frac:.2f}")
Output:
Whole dollars: 123, Cents portion: 0.75
One of the most important behaviors of math.modf() is that both parts retain the sign of the original input. If the input is negative, both the fractional and integer parts are negative:
import math
frac, whole = math.modf(-12.34)
print("Fractional:", frac) # Negative float
print("Integer: ", whole) # Negative float
print("Sum check: ", frac + whole)
Output:
Fractional: -0.33999999999999986
Integer: -12.0
Sum check: -12.34
This behavior preserves the identity frac + whole == x across both positive and negative axes, distinguishing modf() from Python's modulo operator (%) and divmod(), which always return a positive remainder.
In the examples above, you might notice that math.modf(23.45) returned 0.4499999999999993 instead of exactly 0.45. This is not a bug in Python or math.modf(); it is a fundamental property of the IEEE 754 binary floating-point standard.
Just as the fraction 1/3 cannot be represented in a finite number of decimal digits (0.3333...), the decimal number 0.45 cannot be represented exactly in binary powers of two (base 2). If your application requires exact decimal display without floating-point artifacts, clean the result using round() or string formatting:
import math
frac, whole = math.modf(23.45)
clean_frac = round(frac, 4)
print("Raw fraction: ", frac)
print("Clean fraction:", clean_frac)
Output:
Raw fraction: 0.4499999999999993
Clean fraction: 0.45
Python developers frequently encounter multiple methods for extracting whole and decimal parts. The table below compares their outputs on positive and negative values:
| Operation | Input: 7.75 |
Input: -7.75 |
Handling Rule |
|---|---|---|---|
math.modf(x) |
(0.75, 7.0) |
(-0.75, -7.0) |
Returns both parts as floats; inherits sign of x. |
int(x) |
7 |
-7 |
Returns pure integer truncated towards zero. |
math.trunc(x) |
7 |
-7 |
Truncates decimals towards zero (returns integer). |
math.floor(x) |
7 |
-8 |
Rounds downwards towards -∞ (returns integer). |
x % 1.0 |
0.75 |
0.25 |
Floor modulo; result is always ≥ 0.0. |
divmod(x, 1) |
(7.0, 0.75) |
(-8.0, 0.25) |
Uses floor division; remainder is always ≥ 0.0. |
Notice that for negative numbers, math.modf(-7.75) gives the intuitive fractional part -0.75, whereas -7.75 % 1 gives 0.25.
Special IEEE 754 floating-point values are handled predictably:
Input x |
Output (frac, whole) |
Description |
|---|---|---|
0.0 |
(0.0, 0.0) |
Both components are positive zero. |
-0.0 |
(-0.0, -0.0) |
Both components preserve IEEE 754 signed negative zero. |
float('inf') |
(0.0, inf) |
Fractional part is zero; integer part is positive infinity. |
float('-inf') |
(-0.0, -inf) |
Preserves negative sign on both components. |
float('nan') |
(nan, nan) |
Both components are Not-a-Number (NaN). |
import math
print("modf(0.0): ", math.modf(0.0))
print("modf(-0.0): ", math.modf(-0.0))
print("modf(inf): ", math.modf(float('inf')))
print("modf(nan): ", math.modf(float('nan')))
Output:
modf(0.0): (0.0, 0.0)
modf(-0.0): (-0.0, -0.0)
modf(inf): (0.0, inf)
modf(nan): (nan, nan)
In Geographic Information Systems (GIS) and navigation, latitude and longitude coordinates are often given in Decimal Degrees (DD) (e.g. 37.774929). To display them in human-readable navigation charts, they must be converted into Degrees, Minutes, and Seconds (DMS):
math.modf().math.modf() on the total minutes to extract whole minutes and fractional minutes.import math
def dd_to_dms(decimal_degrees, is_latitude=True):
"""
Converts decimal degrees coordinate to (degrees, minutes, seconds, compass_direction).
"""
if not isinstance(decimal_degrees, (int, float)):
raise TypeError("Coordinate must be a numeric value.")
is_negative = decimal_degrees < 0
abs_dd = abs(decimal_degrees)
# 1. Separate degrees and fractional degree
frac_deg, deg = math.modf(abs_dd)
# 2. Convert fractional degree to minutes
total_minutes = frac_deg * 60.0
frac_min, minutes = math.modf(total_minutes)
# 3. Convert fractional minutes to seconds
seconds = round(frac_min * 60.0, 2)
# Determine cardinal direction
if is_latitude:
direction = "S" if is_negative else "N"
else:
direction = "W" if is_negative else "E"
return (int(deg), int(minutes), seconds, direction)
# Unit Assertions
# San Francisco Latitude: 37.7749° N -> 37° 46' 29.64" N
assert dd_to_dms(37.7749, is_latitude=True) == (37, 46, 29.64, 'N')
# San Francisco Longitude: -122.4194° W -> 122° 25' 9.84" W
assert dd_to_dms(-122.4194, is_latitude=False) == (122, 25, 9.84, 'W')
# Prime Meridian / Equator origin check
assert dd_to_dms(0.0) == (0, 0, 0.0, 'N')
# Type verification
try:
dd_to_dms("37.77")
assert False, "Should raise TypeError"
except TypeError:
pass
lat_dms = dd_to_dms(37.7749, True)
lon_dms = dd_to_dms(-122.4194, False)
print(f"San Francisco: {lat_dms[0]}° {lat_dms[1]}' {lat_dms[2]}\" {lat_dms[3]}, {lon_dms[0]}° {lon_dms[1]}' {lon_dms[2]}\" {lon_dms[3]}")
print("All GPS DMS conversion assertions passed successfully!")
Open in Google Colab View on GitHub
Run all math.modf() examples interactively, test sign inheritance, compare with divmod() and math.floor(), and convert geographic GPS coordinates 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.