- Python
Python四捨五入はroundでずれる|切り捨て・切り上げ・Decimal
Pythonの round() は、学校で習う四捨五入ではなく「偶数丸め」をおこなう関数です。 そのため round(2.5) は 3 ではなく 2、round(0.5) は 0 になります。一般的な四捨五入がほしいときは、Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP) と書きます。
この記事では、round(2.5)・round(0.5)・round(2.675, 2) の結果を再現して原因を2つ(偶数丸めと2進小数)に分けて説明し、round・math.floor・math.ceil・int・math.trunc・Decimal.quantize の各丸めモードを、正と負の同じ入力で比べた一覧表にまとめました。コピペ用の四捨五入関数、切り捨て・切り上げ、f文字列の丸め、10の位の丸め、NumPy・pandas の round も扱います。コードは Python 3.14.7 で実行し、出力をそのまま載せています(NumPy 2.4.4・pandas 3.0.2 は Python 3.11.15 で実行)。
Python四捨五入・切り捨て・切り上げの早見表
| やりたいこと | 書き方 | 2.5 | -2.5 | 2.675(小数第2位) |
|---|---|---|---|---|
| 偶数丸め(組み込み) | round(x) / round(x, 2) |
2 | -2 | 2.67 |
| 四捨五入 | Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP) |
3 | -3 | 2.68 |
| 切り捨て(小さいほうへ) | math.floor(x) |
2 | -3 | ― |
| 切り上げ(大きいほうへ) | math.ceil(x) |
3 | -2 | ― |
| 0 に近いほうへ切り捨て | int(x) / math.trunc(x) |
2 | -2 | ― |
| 0 から遠いほうへ切り上げ | Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_UP) |
3 | -3 | 2.68 |
| 表示だけ丸める | f"{x:.2f}" |
2.50 |
-2.50 |
2.67 |
| 10の位で丸める | round(x, -1) |
― | ― | ―(round(25, -1) は 20) |
小数第2位で四捨五入するときは Decimal("1") を Decimal("0.01") に変えます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python四捨五入はroundでずれる|切り捨て・切り上げ・Decimal」
まずはコピペ用:四捨五入する関数
Python 3 の標準ライブラリだけで動きます。次のコードを half_up.py に保存して python half_up.py で実行します。
# half_up.py(Python 3.14.7 で確認)
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x, ndigits=0):
"""一般的な四捨五入。ndigits は小数点以下の桁数(負なら10の位、100の位…)"""
exp = Decimal(1).scaleb(-ndigits) # ndigits=2 → Decimal('0.01')
return Decimal(str(x)).quantize(exp, rounding=ROUND_HALF_UP)
print(round_half_up(2.5)) # 3
print(round_half_up(0.5)) # 1
print(round_half_up(-2.5)) # -3
print(round_half_up(2.675, 2)) # 2.68
print(round_half_up(1.005, 2)) # 1.01
print(round_half_up(1234.5)) # 1235
実行結果:
3
1
-3
2.68
1.01
1235
round() との比較です。
| 入力 | round_half_up |
組み込みの round |
|---|---|---|
2.5 |
3 | 2 |
0.5 |
1 | 0 |
-2.5 |
-3 | -2 |
2.675(小数第2位) |
2.68 | 2.67 |
1.005(小数第2位) |
1.01 | 1.0 |
0.285(小数第2位) |
0.29 | 0.28 |
1234.5 |
1235 | 1234 |
25(10の位) |
30 | 20 |
1250(100の位) |
1300 | 1200 |
ポイントは Decimal(str(x)) と ROUND_HALF_UP の2つです。 戻り値は Decimal 型なので、数値として使うときは float(...) や int(...) で変換します。理由は後半で説明します。
round()が四捨五入とずれることを再現する
for x in [0.5, 1.5, 2.5, 3.5, 4.5, -0.5, -1.5, -2.5]:
print(f"round({x}) =", round(x))
round(0.5) = 0
round(1.5) = 2
round(2.5) = 2
round(3.5) = 4
round(4.5) = 4
round(-0.5) = 0
round(-1.5) = -2
round(-2.5) = -2
ちょうど 0.5 のとき、結果がすべて偶数(0, 2, 4)になっています。 小数第2位で丸めた場合も見てみます。
round(2.675, 2) = 2.67
round(1.005, 2) = 1.0
round(0.125, 2) = 0.12
round(0.375, 2) = 0.38
round(2.665, 2) = 2.67
round(1.15, 1) = 1.1
round(1.25, 1) = 1.2
round(1.35, 1) = 1.4
round(0.285, 2) = 0.28
四捨五入なら 2.68・1.01・0.13 になるところが、2.67・1.0・0.12 になりました。この結果には、別々の原因が2つあります。
| 例 | 結果 | 原因 |
|---|---|---|
round(2.5) → 2、round(0.5) → 0、round(0.125, 2) → 0.12 |
偶数側になる | 原因1:偶数丸め |
round(2.675, 2) → 2.67、round(1.005, 2) → 1.0 |
切り捨てたように見える | 原因2:2進小数の誤差 |
原因1:round()は偶数丸め
公式ドキュメントには、round() について「2つの倍数が同じ近さにある場合は偶数の側に丸める(たとえば round(0.5) と round(-0.5) はどちらも 0、round(1.5) は 2)」と書かれています。これが偶数丸め(最近接偶数への丸め、ROUND_HALF_EVEN)です。
| 値 | 四捨五入 | 偶数丸め | 説明 |
|---|---|---|---|
| 0.5 | 1 | 0 | 0 と 1 の真ん中 → 偶数の 0 |
| 1.5 | 2 | 2 | 1 と 2 の真ん中 → 偶数の 2 |
| 2.5 | 3 | 2 | 2 と 3 の真ん中 → 偶数の 2 |
| 3.5 | 4 | 4 | 3 と 4 の真ん中 → 偶数の 4 |
| 2.4 | 2 | 2 | 真ん中ではないので近いほう |
| 2.6 | 3 | 3 | 真ん中ではないので近いほう |
違いが出るのは「ちょうど真ん中(末尾が 5)」のときだけで、それ以外は四捨五入と同じ結果になります。
なぜ偶数丸めなのか
四捨五入は、真ん中の値を必ず大きいほうへ寄せます。たくさんの値を丸めて合計すると、その分だけ合計が大きいほうへずれていきます。偶数丸めは、真ん中の値を「上へ」と「下へ」に半分ずつ振り分けるので、合計や平均のずれが小さくなります。 0.5 から 9.5 まで1刻みの10個で確かめると、次のようになります。
| 元の値の合計 | 丸めたあとの合計 | |
|---|---|---|
| 四捨五入(1, 2, 3, …, 10) | 50 | 55 |
| 偶数丸め(0, 2, 2, 4, 4, 6, 6, 8, 8, 10) | 50 | 50 |
統計や計測の分野で標準的な方式で、浮動小数点数の規格(IEEE 754)でも既定の丸め方です。Python 3 の round() はこれに従っています。どちらが優れているということではなく、目的に合わせて選ぶものです。
戻り値の型にも注意
print(type(round(2.5)), type(round(2.5, 0)), round(2.5, 0))
<class 'int'> <class 'float'> 2.0
round(x) は int、round(x, 0) は float を返します。 桁数を省略したときだけ整数になります。また round(-0.5, 0) は -0.0 でした。
原因2:2.675は2進小数で正確に表せない
round(2.675, 2) が 2.67 になるのは、偶数丸めのせいではありません(偶数丸めなら 2.68 になるはずです)。2.675 と書いた時点で、コンピューターの中の値は 2.675 よりわずかに小さくなっています。
from decimal import Decimal
print(Decimal(2.675))
print(Decimal("2.675"))
2.67499999999999982236431605997495353221893310546875
2.675
float は数値を2進数で持っています。10進数の 0.1 や 0.675 は、2進数では割り切れずに無限に続く小数になるため、いちばん近い値に置き換えて保存されます。Decimal(2.675) は、その「実際に保存されている値」をそのまま見せてくれます。2.67499999… は真ん中よりわずかに小さいので、近いほうの 2.67 に丸められた、というのが正体です。
ほかの値も確かめました。
| 書いた値 | 実際に保存されている値(先頭部分) | 真ん中より | round(x, n) |
|---|---|---|---|
2.675 |
2.67499999999999982… | 小さい | 2.67 |
1.005 |
1.00499999999999989… | 小さい | 1.0 |
0.285 |
0.28499999999999997… | 小さい | 0.28 |
1.15 |
1.14999999999999991… | 小さい | 1.1 |
2.665 |
2.66500000000000003… | 大きい | 2.67 |
1.35 |
1.35000000000000008… | 大きい | 1.4 |
0.125 |
0.125(正確) | ちょうど | 0.12(偶数丸め) |
1.25 |
1.25(正確) | ちょうど | 1.2(偶数丸め) |
2.5 |
2.5(正確) | ちょうど | 2(偶数丸め) |
0.5・0.25・0.125 のように「2 で割っていって作れる値」は正確に保存され、偶数丸めの対象になります。それ以外は、わずかに大きいか小さいかで結果が決まります。 公式ドキュメントにも「round(2.675, 2) は 2.68 ではなく 2.67 になる。これは不具合ではなく、ほとんどの小数が float では正確に表せないことによる」と明記されています。
Decimal(2.675) と Decimal("2.675") の違い
| 書き方 | 中身 | 小数第2位で ROUND_HALF_UP |
|---|---|---|
Decimal(2.675) |
2.67499999999999982…(float の誤差ごと取り込む) | 2.67 |
Decimal("2.675") |
2.675(書いたとおり) | 2.68 |
Decimal(str(2.675)) |
2.675(str() が最短の表記 '2.675' を返すため) |
2.68 |
from decimal import Decimal, ROUND_HALF_UP
q = Decimal("0.01")
print(Decimal(2.675).quantize(q, rounding=ROUND_HALF_UP),
Decimal("2.675").quantize(q, rounding=ROUND_HALF_UP),
Decimal(str(2.675)).quantize(q, rounding=ROUND_HALF_UP))
2.67 2.68 2.68
Decimal に float を直接渡すと、誤差を含んだ値がそのまま入ります。 文字列で渡すか、すでに float の変数なら str() を挟みます。ROUND_HALF_UP を指定していても、Decimal(2.675) では 2.67 になる点が見落としやすいところです。
丸め方の結果一覧表(正負の同じ入力で比較)
同じ値を、組み込み関数・math・Decimal の各モードで整数に丸めた結果です(Python 3.14.7 で実行)。
組み込み関数と math
| 入力 | round(x) |
math.floor(x) |
math.ceil(x) |
int(x) |
math.trunc(x) |
|---|---|---|---|---|---|
| 2.5 | 2 | 2 | 3 | 2 | 2 |
| -2.5 | -2 | -3 | -2 | -2 | -2 |
| 1.5 | 2 | 1 | 2 | 1 | 1 |
| -1.5 | -2 | -2 | -1 | -1 | -1 |
| 2.4 | 2 | 2 | 3 | 2 | 2 |
| -2.4 | -2 | -3 | -2 | -2 | -2 |
| 2.6 | 3 | 2 | 3 | 2 | 2 |
| -2.6 | -3 | -3 | -2 | -2 | -2 |
| 0.5 | 0 | 0 | 1 | 0 | 0 |
| -0.5 | 0 | -1 | 0 | 0 | 0 |
Decimal.quantize の丸めモード
Decimal(値の文字列).quantize(Decimal("1"), rounding=モード) の結果です。
| 入力 | ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_HALF_DOWN | ROUND_DOWN | ROUND_UP | ROUND_FLOOR | ROUND_CEILING |
|---|---|---|---|---|---|---|---|
| 2.5 | 3 | 2 | 2 | 2 | 3 | 2 | 3 |
| -2.5 | -3 | -2 | -2 | -2 | -3 | -3 | -2 |
| 1.5 | 2 | 2 | 1 | 1 | 2 | 1 | 2 |
| -1.5 | -2 | -2 | -1 | -1 | -2 | -2 | -1 |
| 2.4 | 2 | 2 | 2 | 2 | 3 | 2 | 3 |
| -2.4 | -2 | -2 | -2 | -2 | -3 | -3 | -2 |
| 2.6 | 3 | 3 | 3 | 2 | 3 | 2 | 3 |
| -2.6 | -3 | -3 | -3 | -2 | -3 | -3 | -2 |
| 0.5 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
| -0.5 | -1 | -0 | -0 | -0 | -1 | -1 | -0 |
(-0 は Decimal が符号つきのゼロを保持しているための表示で、値は 0 と等しいです。)
各モードの意味と対応
| モード | 意味 | 対応する関数 | よく使う呼び名 |
|---|---|---|---|
ROUND_HALF_UP |
真ん中は 0 から遠いほうへ | ― | 四捨五入 |
ROUND_HALF_EVEN |
真ん中は偶数へ | round() |
偶数丸め |
ROUND_HALF_DOWN |
真ん中は 0 に近いほうへ | ― | 五捨六入 |
ROUND_DOWN |
0 に近いほうへ | int() / math.trunc() |
切り捨て(絶対値で) |
ROUND_UP |
0 から遠いほうへ | ― | 切り上げ(絶対値で) |
ROUND_FLOOR |
小さいほう(負の無限大)へ | math.floor() |
切り捨て(数直線で) |
ROUND_CEILING |
大きいほう(正の無限大)へ | math.ceil() |
切り上げ(数直線で) |
このほかに ROUND_05UP(丸めた結果の末尾が 0 か 5 になるときだけ 0 から遠いほうへ)もあります。上の入力では、0.5 が 1、-0.5 が -1、それ以外は ROUND_DOWN と同じ結果でした。
表から読み取れる要点は、「負の数で結果が分かれる」ことです。 -2.5 の切り捨ては、math.floor では -3、int では -2 になります。ROUND_HALF_UP は名前に UP とありますが、-2.5 は -3(0 から遠いほう)になります。
切り捨てと切り上げ(floor・ceil・int・trunc)
整数への切り捨て・切り上げ
import math
print(math.floor(2.5), math.floor(-2.5)) # 小さいほうへ
print(math.ceil(2.5), math.ceil(-2.5)) # 大きいほうへ
print(int(2.5), int(-2.5)) # 0 に近いほうへ
print(math.trunc(2.5), math.trunc(-2.5)) # int と同じ
2 -3
3 -2
2 -2
2 -2
| やりたいこと | 正の数だけ | 負の数もある |
|---|---|---|
| 小数部分を取り除く | int(x) |
int(x) / math.trunc(x) |
| その値以下の最大の整数 | int(x) でも同じ |
math.floor(x) |
| その値以上の最小の整数 | math.ceil(x) |
math.ceil(x) |
| 絶対値を大きくする方向へ | math.ceil(x) |
Decimal の ROUND_UP |
正の数だけなら int() と math.floor() は同じ結果ですが、負の数が混ざると違いが出ます。 整数どうしの割り算 // も floor と同じ向きです。
print(-7 // 2, 7 // 2, int(-7 / 2), divmod(-7, 2))
-4 3 -3 (-4, 1)
小数第 n 位での切り捨て・切り上げ
math.floor と math.ceil には桁数の引数がありません。「100倍して丸め、100で割る」書き方がよく使われます。
import math
x = 1234.5678
print(math.floor(x * 100) / 100, math.ceil(x * 100) / 100)
print(math.floor(-x * 100) / 100, math.trunc(-x * 100) / 100)
1234.56 1234.57
-1234.57 -1234.56
ただし、掛け算の時点で2進小数の誤差が表に出ることがあります。
print(math.floor(4.35 * 100), 4.35 * 100)
print(math.ceil(1.1 * 100), 1.1 * 100)
434 434.99999999999994
111 110.00000000000001
4.35 を小数第2位で切り捨てたいのに、4.35 * 100 が 434.999… になり、結果は 4.34 相当になってしまいます。1.1 の切り上げも 1.11 相当になります。桁を指定した切り捨て・切り上げも、Decimal に任せると書いたとおりの結果になります。
from decimal import Decimal, ROUND_DOWN, ROUND_CEILING, ROUND_UP, ROUND_FLOOR
print(Decimal("4.35").quantize(Decimal("0.01"), rounding=ROUND_DOWN))
print(Decimal("1.1").quantize(Decimal("0.01"), rounding=ROUND_CEILING))
print(Decimal("1234.5678").quantize(Decimal("0.01"), rounding=ROUND_DOWN))
print(Decimal("1234.5611").quantize(Decimal("0.01"), rounding=ROUND_UP))
print(Decimal("-1234.5678").quantize(Decimal("0.01"), rounding=ROUND_FLOOR))
4.35
1.10
1234.56
1234.57
-1234.57
コピペ用:桁指定の切り捨て・切り上げ関数
from decimal import Decimal, ROUND_DOWN, ROUND_UP
def round_down(x, ndigits=0):
"""0 に近いほうへ切り捨て"""
return Decimal(str(x)).quantize(Decimal(1).scaleb(-ndigits), rounding=ROUND_DOWN)
def round_up(x, ndigits=0):
"""0 から遠いほうへ切り上げ"""
return Decimal(str(x)).quantize(Decimal(1).scaleb(-ndigits), rounding=ROUND_UP)
負の数を「小さいほうへ」「大きいほうへ」そろえたいときは、ROUND_FLOOR・ROUND_CEILING に置き換えます。
整数の10の位・100の位での切り捨て・切り上げ
整数なら、誤差の心配なく // で書けます。
print(1239 // 10 * 10) # 10の位未満を切り捨て
print(-(-1231 // 10) * 10) # 10の位未満を切り上げ
1230
1240
Decimal.quantizeの使い方
quantize() は、「この形にそろえる」という見本を第1引数に渡すメソッドです。
| そろえる位置 | 見本 | scaleb での作り方 |
|---|---|---|
| 整数 | Decimal("1") |
Decimal(1).scaleb(0) |
| 小数第1位 | Decimal("0.1") |
Decimal(1).scaleb(-1) |
| 小数第2位 | Decimal("0.01") |
Decimal(1).scaleb(-2) |
| 10の位 | Decimal("1E1") |
Decimal(1).scaleb(1) |
| 100の位 | Decimal("1E2") |
Decimal(1).scaleb(2) |
Decimal(1).scaleb(-2) が Decimal('0.01')、Decimal(1).scaleb(1) が Decimal('1E+1') になることを実行して確かめています。
10の位以上で丸めると指数表記になる
r = round_half_up(1250, -2)
print(repr(r), int(r), float(r))
Decimal('1.3E+3') 1300 1300.0
10の位や100の位で丸めた Decimal は 1.3E+3 のような表記になります。値は 1300 と等しく、int() で整数に変換すれば通常の表示に戻ります。
rounding を省略すると偶数丸め
from decimal import Decimal, getcontext
print(round(Decimal("2.5")), round(Decimal("2.675"), 2), getcontext().rounding)
2 2.68 ROUND_HALF_EVEN
Decimal の既定の丸めモードも ROUND_HALF_EVEN です。round(Decimal("2.5")) は 2 になります。round(Decimal("2.675"), 2) が 2.68 なのは、真ん中の値を偶数側(8)へ丸めた結果で、四捨五入と一致したのは偶然です。四捨五入にしたいときは、rounding=ROUND_HALF_UP を毎回明示します。
Decimal でつまずきやすい点
| コード | 結果 | 対処 |
|---|---|---|
Decimal(2.675) |
誤差ごと取り込む | Decimal("2.675") / Decimal(str(x)) |
Decimal("1.5") + 1.5 |
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float' |
相手も Decimal にする(int とは計算できる) |
Decimal("abc") |
InvalidOperation: [<class 'decimal.ConversionSyntax'>] |
入力を検証する |
29桁以上になる quantize |
InvalidOperation |
getcontext().prec を大きくする(既定は 28 桁) |
quantize の rounding を省略 |
偶数丸めになる | rounding=ROUND_HALF_UP を書く |
四捨五入の自作でよく見る書き方との比較
math.floor(x + 0.5) で四捨五入を作る方法もよく紹介されています。結果を比べました。
import math
def naive(x, n=0):
return math.floor(x * 10**n + 0.5) / 10**n
| 入力 | naive の結果 |
round_half_up の結果 |
補足 |
|---|---|---|---|
2.5 |
3.0 | 3 | 一致 |
2.675(第2位) |
2.68 | 2.68 | 一致 |
1.005(第2位) |
1.0 | 1.01 | 1.005 * 100 が 100.49999999999999 になる |
0.285(第2位) |
0.28 | 0.29 | 0.285 * 100 が 28.499999999999996 になる |
-2.5 |
-2.0 | -3 | 負の数は大きいほうへ寄る |
0.49999999999999994 |
1.0 | 0 | 0.5 を足した時点で 1.0 になる |
正の数の多くでは合いますが、掛け算と足し算で誤差が入る値や、負の数では結果が変わります。 入力の範囲が限られていて検証済みなら使えますが、汎用の関数としては Decimal(str(x)) と ROUND_HALF_UP の組み合わせが確実です。
f文字列・formatの丸めも偶数丸め
表示用に桁をそろえる f"{x:.2f}" も、round() と同じ丸め方をします。
for x in [0.5, 1.5, 2.5, 2.675, 1.005, 0.125, 2.665]:
print(x, f"{x:.0f}", f"{x:.2f}", format(x, ".1f"), "%.2f" % x)
0.5 0 0.50 0.5 0.50
1.5 2 1.50 1.5 1.50
2.5 2 2.50 2.5 2.50
2.675 3 2.67 2.7 2.67
1.005 1 1.00 1.0 1.00
0.125 0 0.12 0.1 0.12
2.665 3 2.67 2.7 2.67
| 書き方 | 結果 | 説明 |
|---|---|---|
f"{0.5:.0f}" |
'0' |
偶数丸め |
f"{2.5:.0f}" |
'2' |
偶数丸め |
f"{0.125:.2f}" |
'0.12' |
偶数丸め |
f"{2.675:.2f}" |
'2.67' |
2進小数の誤差 |
f"{1234.5:,.0f}" |
'1,234' |
桁区切りつきでも同じ |
f"{0.125:.1%}" |
'12.5%' |
パーセント表示 |
f"{x:.2f}"・format(x, ".2f")・"%.2f" % x・"{:.2f}".format(x) は、どれも同じ結果でした。
四捨五入してから表示する
四捨五入した結果を表示したいときは、先に Decimal で丸めてから文字列にします。
from decimal import Decimal, ROUND_HALF_UP
x = 2.675
d = Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(f"{d}") # 2.68
print(f"{d:,.2f}") # 2.68(桁区切りなどの書式も使える)
なお、Decimal を f文字列の桁指定だけで丸めると、偶数丸めになります。
print(f"{Decimal('2.675'):.2f}", f"{Decimal('2.5'):.0f}",
f"{Decimal('0.125'):.2f}", f"{Decimal('3.5'):.0f}")
2.68 2 0.12 4
Decimal('2.5') が 2、Decimal('0.125') が 0.12 になっています。書式指定に丸めを任せず、quantize で丸めてから表示する、という順番にすると結果がそろいます。
桁を指定して丸める:round(x, -1)で10の位
round() の第2引数に負の数を渡すと、整数部分の桁で丸められます。
| 書き方 | 丸める位置 | 例 |
|---|---|---|
round(x, 2) |
小数第2位まで残す | round(1234.5678, 2) → 1234.57 |
round(x, 0) |
整数(float のまま) | round(2.5, 0) → 2.0 |
round(x, -1) |
10の位まで残す | round(1234.5678, -1) → 1230.0 |
round(x, -2) |
100の位まで残す | round(1234.5678, -2) → 1200.0 |
round(x, -3) |
1000の位まで残す | round(1234.5678, -3) → 1000.0 |
整数の丸めも偶数丸め
round(15, -1) = 20
round(25, -1) = 20
round(35, -1) = 40
round(45, -1) = 40
round(125, -1) = 120
round(14, -1) = 10
round(16, -1) = 20
round(1250, -2) = 1200
round(1350, -2) = 1400
round(25, -1) は 30 ではなく 20、round(1250, -2) は 1300 ではなく 1200 です。 整数には2進小数の誤差がないので、末尾がちょうど 5(50、500…)のときは必ず偶数側になります。整数を渡すと戻り値も int、小数を渡すと float です。
四捨五入にしたいときは、冒頭の関数を使います。
print(int(round_half_up(25, -1)), int(round_half_up(1250, -2))) # 30 1300
NumPy・pandasのroundも偶数丸め
配列やデータフレームの round も、ちょうど真ん中の値は偶数側に丸めます(Python 3.11.15、NumPy 2.4.4、pandas 3.0.2 で確認)。
import numpy as np
import pandas as pd
a = np.array([0.5, 1.5, 2.5, 3.5, -0.5, -1.5, -2.5])
print(np.round(a))
print(np.round(np.array([15, 25, 35, 1250]), -1))
s = pd.Series([0.5, 1.5, 2.5, 3.5, -2.5, 2.675])
print(s.round().tolist())
print(s.round(2).tolist())
[ 0. 2. 2. 4. -0. -2. -2.]
[ 20 20 40 1250]
[0.0, 2.0, 2.0, 4.0, -2.0, 3.0]
[0.5, 1.5, 2.5, 3.5, -2.5, 2.68]
a.round()・np.around(a)・np.rint(a) も同じ結果でした。
組み込みの round と結果が違う値がある
| 入力 | 組み込み round(x, 2) |
np.round(x, 2) / Series.round(2) |
|---|---|---|
| 2.675 | 2.67 | 2.68 |
| 1.005 | 1.0 | 1.0 |
| 0.125 | 0.12 | 0.12 |
| 0.375 | 0.38 | 0.38 |
2.675 は、組み込みの round では 2.67、NumPy・pandas では 2.68 になりました。 NumPy のドキュメントには、小数の桁を指定した丸めは「10 のべき乗を掛けて丸め、割り戻す」高速な方法でおこなうため、誤差が出ることがあると書かれています。2.675 * 100 が 267.5 になり、それを偶数丸めして 268 になった、という流れです。「NumPy は四捨五入してくれる」というわけではなく、0.125 は 0.12 のままです。
NumPy・pandas で四捨五入・切り捨て・切り上げ
print(np.floor(a)) # 小さいほうへ
print(np.ceil(a)) # 大きいほうへ
print(np.trunc(a)) # 0 に近いほうへ(np.fix も同じ結果)
print(np.sign(a) * np.floor(np.abs(a) + 0.5)) # 四捨五入(0 から遠いほうへ)
[ 0. 1. 2. 3. -1. -2. -3.]
[ 1. 2. 3. 4. -0. -1. -2.]
[ 0. 1. 2. 3. -0. -1. -2.]
[ 1. 2. 3. 4. -1. -2. -3.]
np.floor(a + 0.5) だけだと、負の数は [..., 0, -1, -2] と大きいほうへ寄りました。np.sign と np.abs を組み合わせると、正負とも 0 から遠いほうへそろいます。
小数の桁で厳密に四捨五入したい列は、要素ごとに Decimal を使う方法が確実です。
from decimal import Decimal, ROUND_HALF_UP
half_up = lambda x: float(Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
print(s.map(half_up).tolist())
[1.0, 2.0, 3.0, 4.0, -3.0, 3.0]
pandas の DataFrame.round({"a": 2, "b": 0}) のように、列ごとに桁数を変える書き方も偶数丸めでした(0.125 → 0.12、2.5 → 2.0)。
どの方法を選ぶかの判断表
| 状況 | 選ぶ方法 |
|---|---|
| グラフや画面表示で、桁をそろえたいだけ | f"{x:.2f}" / round(x, 2) |
| 統計・計測で、合計や平均のずれを抑えたい | round()(偶数丸め) |
| 帳票や集計で、一般的な四捨五入にそろえる決まりがある | Decimal(str(x)) + ROUND_HALF_UP |
| 小数の計算そのものを誤差なくおこないたい | 最初から Decimal("...") で持つ |
| 切り捨て・切り上げ(整数へ) | math.floor / math.ceil / int |
| 切り捨て・切り上げ(桁指定) | Decimal.quantize + ROUND_DOWN / ROUND_UP |
| 配列をまとめて丸める | np.round(偶数丸め)/四捨五入は np.sign と np.floor |
| float どうしが等しいか比べたい | math.isclose(a, b)(丸めて比べない) |
確認用のテスト値
自作の丸め関数や、ほかの言語から移した処理を確かめるときは、次の値を通すと方式の違いがすぐわかります。
| 入力 | 四捨五入の期待値 | この値で確かめられること |
|---|---|---|
0.5 / 2.5 |
1 / 3 | 偶数丸めになっていないか |
-2.5 |
-3 | 負の数の向き |
2.675(第2位) |
2.68 | float の誤差を取り込んでいないか |
1.005(第2位) |
1.01 | 掛け算で誤差が出ていないか |
25(10の位) |
30 | 整数の桁丸め |
よくある質問
Q. round(2.5) が 2 になるのは不具合ですか?
A. 仕様どおりの動作です。Python 3 の round() は、ちょうど真ん中の値を偶数側へ丸める「偶数丸め」を採用しており、公式ドキュメントにも round(0.5) が 0、round(1.5) が 2 になると書かれています。四捨五入がほしいときは、Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP) を使います。
Q. round(2.675, 2) が 2.67 になるのも偶数丸めのせいですか?
A. いいえ、こちらは2進小数の誤差が原因です。Decimal(2.675) で確かめると、実際の値は 2.67499999999999982… で、真ん中よりわずかに小さいため 2.67 に丸められます。Decimal("2.675") のように文字列から作れば、2.675 ちょうどとして扱えます。
Q. int() と math.floor() はどう違いますか?
A. 負の数で結果が変わります。int(-2.5) は 0 に近いほうの -2、math.floor(-2.5) は小さいほうの -3 です。正の数ではどちらも同じ結果になります。「小数部分を取り除く」なら int、「その値以下の最大の整数」なら math.floor を選びます。
Q. 小数第2位で四捨五入するには、どう書けばよいですか?
A. Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) です。この記事の round_half_up(x, 2) をコピーして使えます。結果は Decimal 型なので、float が必要なら float(...) で変換します。
まとめ
round()は偶数丸め。round(2.5)は 2、round(0.5)は 0、round(25, -1)は 20round(2.675, 2)が 2.67 になるのは2進小数の誤差。実際の値は 2.67499999… になっている- 四捨五入は
Decimal(str(x)).quantize(..., rounding=ROUND_HALF_UP)。float を直接Decimal()に渡さない - 切り捨ては
math.floor(小さいほう)とint(0 に近いほう)で、負の数の結果が違う - 桁指定の切り捨て・切り上げも Decimal が確実。
math.floor(4.35 * 100)は 434 になった - f文字列・format・NumPy・pandas の丸めも偶数丸め。四捨五入は丸めてから表示する
- 確認には 0.5・2.5・-2.5・2.675・1.005 を通すと、方式の違いがすぐわかる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python四捨五入はroundでずれる|切り捨て・切り上げ・Decimal」
株式会社RJCでは、Pythonの数値の丸めや小数の扱いを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。