Pythonのmath.modf()で整数部と小数部を分ける|負の数・divmodとの違い・誤差の対処

Pythonで数値の整数部と小数部を一度に取り出すには、math.modf(x)を使います。戻り値は(小数部, 整数部)の順のタプルで、どちらもfloat型、符号は元の数と同じです。

この記事のコードはPython 3.12.15で実行し、実際の出力を載せています(NumPyの例のみPython 3.9.6・NumPy 2.0.2)。関数の仕様はPython公式ドキュメントのmathモジュール(2026年10月17日確認)に沿っています。

math.modf()の基本

math.modf()は引数を1つ受け取り、小数部と整数部を2つのfloatで返します。順番が「小数部が先」なので、受け取るときに逆にしないよう注意してください。

import math

print(math.modf(3.14))
frac, whole = math.modf(3.14)
print(whole, frac)
print(type(whole), type(frac))

実行結果:

(0.14000000000000012, 3.0)
3.0 0.14000000000000012
<class 'float'> <class 'float'>

小数部が0.14ではなく0.14000000000000012になるのは、3.14という値が2進数の浮動小数点数では正確に表せないためです。math.modf()の不具合ではありません。この誤差の理由は浮動小数点数とは?符号部・指数部・仮数部で詳しく説明しています。

戻り値の仕様(公式ドキュメントより)

項目内容
戻り値(小数部, 整数部) のタプル
型どちらもfloat(int を渡しても float で返る)
符号どちらも元の数 x と同じ符号
C言語との違いCの modf は整数部をポインタ引数で返すが、Pythonではタプルで両方を返す

正の数・負の数・整数を渡したとき

import math

for x in [3.75, -3.75, 5, -0.5, 2.0]:
    print(x, math.modf(x))

実行結果:

3.75 (0.75, 3.0)
-3.75 (-0.75, -3.0)
5 (0.0, 5.0)
-0.5 (-0.5, -0.0)
2.0 (0.0, 2.0)

ポイントは3つです。

  • -3.75は(-0.75, -3.0)になり、小数部も整数部もマイナスです。
  • int の5を渡しても(0.0, 5.0)とfloatで返ります。
  • -0.5の整数部は-0.0(マイナスのゼロ)になります。-0.0 == 0.0はTrueなので比較では困りませんが、表示すると「-0.0」と出るので、画面に出すときはint()で変換しておくと安全です。

int()・math.floor()・%・divmod()との違い

整数部や小数部を取り出す方法はほかにもあります。正の数では結果が同じでも、負の数では答えが分かれます。

import math

x = -3.75
print("modf      :", math.modf(x))
print("int       :", int(x))
print("math.trunc:", math.trunc(x))
print("math.floor:", math.floor(x))
print("x % 1     :", x % 1)
print("divmod    :", divmod(x, 1))
print("math.fmod :", math.fmod(x, 1))

実行結果:

modf      : (-0.75, -3.0)
int       : -3
math.trunc: -3
math.floor: -4
x % 1     : 0.25
divmod    : (-4.0, 0.25)
math.fmod : -0.75
書き方-3.75 の結果丸めの向き・特徴
math.modf(x)(-0.75, -3.0)0に向かって切り捨て。小数部と整数部を同時に返す
int(x)・math.trunc(x)-30に向かって切り捨て。整数部だけをint型で返す
math.floor(x)-4小さいほうへ切り捨て(-3.75以下の最大の整数)
x % 10.25floor基準の余り。結果は常に0以上
divmod(x, 1)(-4.0, 0.25)floor基準の商と余り。順番は(整数部, 小数部)
math.fmod(x, 1)-0.75元の数と同じ符号の余り(modfの小数部と同じ)

「-3.75を-3と-0.75に分けたい」ならmath.modf()、「-4と0.25に分けたい」(たとえば時刻や角度を常に正の端数で扱いたい)ならdivmod(x, 1)を使う、と覚えておくと迷いません。divmod()と%の負の数での動きはPythonのdivmod()で負の数・小数を割るとどうなる?で詳しく比べています。

無限大・NaN・とても大きな数

import math

for x in [float("inf"), float("-inf"), float("nan"), pow(2.0, 53) + 0.5, 1e20]:
    print(x, math.modf(x))

実行結果:

inf (0.0, inf)
-inf (-0.0, -inf)
nan (nan, nan)
9007199254740992.0 (0.0, 9007199254740992.0)
1e+20 (0.0, 1e+20)

無限大を渡すと小数部は0.0(マイナス無限大なら-0.0)、NaNは両方NaNになります。公式ドキュメントには、floatの精度はふつう53ビットなので、絶対値が2の52乗以上のfloatには小数部が存在しないという注意書きがあります。上の例でpow(2.0, 53) + 0.5の0.5が消えているのはこのためで、足した時点で0.5が表現できずに失われています。

小数部の誤差を避けたいとき

お金や測定値など、小数部を「見たとおりの値」で扱いたい場面では、floatの誤差が問題になります。

import math
from decimal import Decimal

print(math.modf(1.1))
print(math.modf(123.45))

frac, whole = math.modf(123.45)
print(round(frac, 2))

d = Decimal("123.45")
print(d % 1, d // 1)

実行結果:

(0.10000000000000009, 1.0)
(0.45000000000000284, 123.0)
0.45
0.45 123

対処は2通りです。

  • 表示や比較のために丸める:round(frac, 2)のように、必要な桁で丸めてから使います。
  • 最初から10進数で計算する:decimal.Decimalを文字列から作れば、d % 1とd // 1で誤差のない小数部と整数部が得られます(負の数ではDecimalの%と//は0に向かって切り捨てる点がfloatと違います)。

丸め方の違い(偶数丸めなど)はPythonで四捨五入する方法にまとめています。

活用例1:時間(小数)を「○時間○分」に変換する

勤怠や作業時間のデータで「1.5時間」のような小数の時間を表示用に変換する例です。小数部に60を掛けて分にします。

import math

def hours_to_hm(hours):
    frac, whole = math.modf(hours)
    minutes = round(frac * 60)
    h = int(whole)
    if minutes == 60:
        h, minutes = h + 1, 0
    return f"{h}時間{minutes}分"

for t in [1.5, 2.25, 7.9999, 0.1]:
    print(t, "->", hours_to_hm(t))

実行結果:

1.5 -> 1時間30分
2.25 -> 2時間15分
7.9999 -> 8時間0分
0.1 -> 0時間6分

7.9999時間は分に直すと「7時間60分」になってしまうため、60分になったら時間に繰り上げる処理を入れています。端数を丸める処理を入れるときは、このような繰り上がりを忘れがちです。

活用例2:緯度・経度を「度・分・秒」に変換する

10進数の緯度経度を、地図でよく使う度分秒(60進数)に変換する例です。math.modf()を2回使い、度→分→秒と順に分解します。

import math

def to_dms(deg):
    frac, d = math.modf(abs(deg))
    frac, m = math.modf(frac * 60)
    s = frac * 60
    sign = "-" if deg < 0 else ""
    return f"{sign}{int(d)}度{int(m)}分{s:.2f}秒"

print(to_dms(35.681236))   # 東京駅付近の緯度
print(to_dms(139.767125))  # 東京駅付近の経度

実行結果:

35度40分52.45秒
139度46分1.65秒

負の緯度経度(南緯・西経)でも正しく分解できるよう、絶対値で計算してから符号を付けています。math.modf()は符号を残すので、そのまま使うと分や秒までマイナスになるためです。

活用例3:整数かどうかを判定する

import math

def is_whole(x):
    return math.modf(x)[0] == 0.0

print(is_whole(4.0), is_whole(4.5))
print((4.0).is_integer(), (4.5).is_integer())

実行結果:

True False
True False

小数部が0かどうかで整数判定もできますが、floatならis_integer()メソッドのほうが意図が伝わりやすい書き方です。整数判定の方法はPythonで整数か判定する方法にまとめています。

NumPy配列ならnp.modf()

配列の要素をまとめて分解するときは、NumPyのnp.modf()を使います。NumPyの公式ドキュメント(2026年10月17日確認)のとおり、要素ごとに小数部と整数部を返し、負の数では両方が負になります。

import numpy as np

a = np.array([3.75, -3.75, 0.5, 10.0])
frac, whole = np.modf(a)
print(frac)
print(whole)

実行結果:

[ 0.75 -0.75  0.5   0.  ]
[ 3. -3.  0. 10.]

戻り値の順番はmath.modf()と同じく「小数部、整数部」です。mathモジュールのほかの関数はPythonのmath関数一覧で確認できます。

よくある質問

Q. math.modf()の戻り値の順番は?

(小数部, 整数部) の順のタプルです。frac, whole = math.modf(x) のように小数部を先に受け取ります。divmod(x, 1) は (整数部, 小数部) の順なので逆です。

Q. math.modf()で整数部をint型で取得できますか?

できません。整数部もfloatで返るため、int型が必要なら int(whole) のように変換します。整数部だけが欲しいなら int(x) や math.trunc(x) でも同じ結果になります。

Q. 負の数を渡すとどうなりますか?

小数部も整数部も元の数と同じ符号になります。math.modf(-3.75) は (-0.75, -3.0) です。-4 と 0.25 に分けたい場合は divmod(x, 1) を使います。

Q. 小数部が0.14000000000000012のようにずれるのはなぜですか?

3.14のような値が2進数の浮動小数点数で正確に表せないためで、math.modf()の不具合ではありません。round()で丸めるか、decimal.Decimalで計算すると避けられます。

Q. NumPy配列に使えますか?

配列には np.modf() を使います。要素ごとに小数部と整数部の配列を返し、順番は math.modf() と同じです。

あわせて読みたい

学習の順序はPython学習のロードマップ、標準で使える機能の一覧はPython標準ライブラリのモジュール一覧にまとめています。

Pythonの関数・メソッドの全体像はPython基本関数・メソッド完全一覧にまとめています。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら