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) は 20
  • round(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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?