Python
2022.05.20
Pythonのround関数!四捨五入の結果が違う?よくある疑問を解決
2023.11.19

数字を扱う上で欠かせない「四捨五入」や「切り上げ」「切り捨て」の処理。


Pythonの場合、人間が思っている結果と異なる値を出力する場合があります。


参考書によっては載っていない内容になりますので、独学されている方は必見ですよ。


1.Pythonのround()関数とは?

round()関数は、Pythonの標準関数(組み込み関数)のひとつです。


公式ドキュメントでは以下のようにround()関数を紹介しています。


「小数点以下を指定桁数に丸めた値を返す」

組み込み関数 — Python 3.10.4 ドキュメント

一見すると私たち人間がイメージする四捨五入と同じ結果を出力してくれそうですが、入力値によっては以下のように人間とは違う結果を出力します。


例えば

入力値 1.125
丸める桁数 2


プログラム

print( round(1.125, 2) )

結果

1.12

小数点以下3桁目の5を四捨五入すると繰り上がって1.13になりそうですが、Pythonのround()関数は上記の出力1.12が標準です。


なぜこのような結果を出力するか、どのように対応すればいいかなどをご紹介していきます。

 2.round()関数の使い方


round()関数は、pip install なしですぐに使える関数です。基本的な書き方をご紹介します。

基本構文

round()関数には、2つの引数を設定できます。

#サンプルコード
round(1.234, 3)

第1引数は整数型もしくはフロート型を入力し、第2引数は整数型を入力します。


処理結果のデータ型は、小数点以下がある場合はフロート型、小数点以下がない場合は整数型の値が返ってきます。

以下に詳しくご紹介していきます。

第1引数のみ指定して整数に丸める

round()関数を第2引数なしで使用した場合、以下のような整数に四捨五入されます。

case_1 = round(1.2)
case_2 = round(1.55)
case_3 = round(2.5)
case_4 = round(999999999999999999999.999999999999999)

print(case_1)
print(case_2)
print(case_3)
print(case_4)
print(type(case_1))

結果

1
2
2
1000000000000000000000
<class 'int'>

小数点以下1桁目の値によって四捨五入されています。


ただし、3つ目の値「2.5」は四捨五入すると「3」になるはずですが、Pythonは「2」を出力しています。不思議ですね。


これについては後述します。

③第2引数を指定して任意の桁に丸める

round()関数の第2引数を設定して、四捨五入を行ってみます。このケースも、先程同様に少し注意が必要です。

第2引数が正のとき

round()関数の第2引数に「正(プラス)」の値を設定して、実行してみます。

今回は、小数点以下1桁で丸めるように、「1」を第2引数に設定しました。

case_1 = round(1.2, 1)
case_2 = round(1.25, 1)
case_3 = round(2, 1)
case_4 = round(999999999999999999999.999999999999999, 1)

print(case_1)
print(case_2)
print(case_3)
print(case_4)
print(type(case_1))

結果

1.2
1.2
2
1e+21
<class 'float'>

第1引数の値によって、さまざまな処理結果を取得できました。

この中で「1.25」の四捨五入値が「1.2」。あれ、「1.3」になるべきではないでしょうか?

第2引数が負のとき

四捨五入というと小数点以下を処理するイメージが強いですが、プログラムの世界では整数部分の四捨五入もできます。

case_0 = round(154, -1)
case_1 = round(154, -2)
case_2 = round(12345, -1)
case_3 = round(12345.678, -1)
case_4 = round(10000, -1)
case_5 = round(999999999999999999999.999999999999999, -1)

print(case_0)
print(case_1)
print(case_2)
print(case_3)
print(case_4)
print(type(case_1))

結果

150
200
12340
12350.0
10000
<class 'int'>

round()関数の第2引数に「-1」を設定することで、1の位を四捨五入します。「-2」を設定すると10の位を四捨五入。私たちの日常生活ではあまり使わない四捨五入になりますが、データ処理を扱う上では役に立つ処理ですね。


上記プログラムの変数case_2とcase_3、どちらも同じ1の位を四捨五入するように設定していますが、出力結果はどうでしょうか?


思っていた結果と異なる出力をする場合もあるround()関数も正しく使えば、安心して利用できます。


下記項目を参考にしてください。



3.round()関数の結果が予想とちがう理由は?

round(1.25, 1)が「1.2」、round(12345, -1)が「12340」など、私たち人間とは異なる結果を出力する理由をご紹介します。

①「銀行家の丸め」を使っているから

Pythonのround()関数は、通称「銀行家の丸め」と同じ結果を出力します。


「銀行家の丸め」とは、四捨五入を判定する値が「5」の場合、絶対的に数字を繰上げ処理するのではなく、結果が偶数になる方に丸める、という処理です。


実際にプログラムを見てみましょう。

n_1 = round(1.25, 1)
n_2 = round(1.35, 1)
n_3 = round(1.45, 1)
n_4 = round(1.55, 1)
n_5 = round(1.15, 1)

print(n_1)
print(n_2)
print(n_3)
print(n_4)
print(n_5)
print(type(n_1))

結果

1.2
1.4
1.4
1.6
1.1
<class 'float'>

「1.25」や「1.45」は、小数点2桁目を四捨五入すると結果が「1.3」「1.5」と奇数になりますが、Pythonのround()関数では偶数となる「1.2」や「1.4」を出力します。


ただ「1.15」も同じ理屈で考えると「1.2」になりそうですが、結果は「1.1」。あれ、「1.1」は奇数では、とツッコミたくなりますよね。


Pythonのround()関数が、なぜこのような処理結果を出力するか、Python側の事情を確認してみましょう。

②2進数では小数点以下を表現しきれないことがあるから

私たち人間の世界の数字は「10進数」です。Pythonをはじめプログラムが動くコンピューターの世界では、数字は「2進数」です。


見た目は同じ「1.25」でも、数字の基準が異なります。

そして2進数では、10進数の小数点以下を正しく再現できないケースがほとんどです。


・1.25の10進数 1.25

・1.25の2進数 1.01


・1.15の10進数 1.15

・1.15の2進数 1.0010011001・・・

このように画面上は「1.25」「1.15」と見えている数字も、コンピューターの内部的には一度2進数化を経て10進数として表示されています。



その結果、「1.25」は「1.25000000000・・・」ですが、「1.15」は「1.149999999999999

・・・」という数字がコンピューターの持つ本当の値です。


Pythonのround()関数を用いた場合も、一度2進数化を経て四捨五入されるため、私たちがイメージする結果とは異なる数字を出力する場合がある、ということになります。


ただ、コンピューターやPythonの都合で思い通りに四捨五入を扱えないのは不便ですよね。

以下にround()関数とは違う方法で四捨五入する方法をご紹介します。

4.一般的な四捨五入をするには?


Pythonの標準モジュール「decimal(デシマル)」を使うと、正確な四捨五入値を取得できます。

①decimalモジュールのROUND_HALF_UPモードを使う

decimalモジュールは、四捨五入や切り上げなどの丸め目規則を8種類、演算過程で生じる例外処理を9種類もつモジュールになります。


decimalモジュールを用いて四捨五入する方法をご紹介します。


基本的なdecimalモジュールの書き方

Decimal(文字型の値).quantize( Decimal(丸める桁数), rounding=丸め方 )

1.25を小数点以下2桁目で四捨五入する場合は、以下のようになります。

from decimal import Decimal, ROUND_HALF_UP
n_str = str(1.25)
result = Decimal( n_str ).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)
print(result)

結果

1.3

round()関数では正しく四捨五入できなかった「1.25」も、「1.3」と予定通りの結果を出力することができました。


丸める桁数は、上記では「0.1」と小数点以下1桁に丸める処理を行いました。小数点以下2桁に丸めたい場合は「0.01」、小数点以下3桁に丸める場合は「0.001」と記述します。

②formatも「銀行家の丸め」を使っているので注意

文字列の中に効率よく変数値を割り当てる方法に「formatメソッド」があります。

formatメソッドの中にも四捨五入をする機能はありますが、round()関数と同じ結果を返してしまうのです。

n_format_1 = "{:.1f}".format(1.23)
n_format_2 = "{:.1f}".format(1.25)
print(n_format_1)
print(n_format_2)

結果

1.2
1.2

文章内に四捨五入した正確な値を入れたい場合は、一度decimalモジュールで処理して、それから文字列内に挿入するようにしましょう。



5.切り捨て、切り上げをするには?


数字を切り捨て、切り上げする方法を4種類ご紹介します。

①「0に近づける」切り捨て

小数点以下を切り捨てる方法には、int()関数とdecimalモジュールのROUND_DOWNがあります。

この二つの特徴は、フロート型が正の場合も負の場合も、0に近づくように切り捨てられるというところです。

int()

整数型に変換するint()関数を用いると、0に近づく切り捨てを実行します。

n_float_plus_1 = 1.25
n_float_plus_2 = 1.84
n_float_minus_1 = -1.25
n_float_minus_2 = -1.84

n_int_plus_1 = int(n_float_plus_1)
n_int_plus_2 = int(n_float_plus_2)
n_int_minus_1 = int(n_float_minus_1)
n_int_minus_2 = int(n_float_minus_2)

print(n_int_plus_1)
print(n_int_plus_2)
print(n_int_minus_1)
print(n_int_minus_2)

結果

1  
1
-1
-1

正の値も負の値も単純に、小数点以下が切り捨てられていることが確認できました。

decimalモジュールのROUND_DOWNモード

任意の小数点で切り捨てを行いたい場合は、decimalモジュールを活用できます。

from decimal import Decimal, ROUND_DOWN

n_float_plus_1 = 1.25
n_float_plus_2 = 1.86
n_float_minus_1 = -1.25
n_float_minus_2 = -1.86

n_str_plus_1 = str(n_float_plus_1)
n_str_plus_2 = str(n_float_plus_2)
n_str_minus_1 = str(n_float_minus_1)
n_str_minus_2 = str(n_float_minus_2)

n_str_plus_1 = Decimal( n_str_plus_1 ).quantize(Decimal('0.1'), rounding=ROUND_DOWN)
n_str_plus_2 = Decimal( n_str_plus_2 ).quantize(Decimal('0.1'), rounding=ROUND_DOWN)
n_str_minus_1 = Decimal( n_str_minus_1 ).quantize(Decimal('0.1'), rounding=ROUND_DOWN)
n_str_minus_2 = Decimal( n_str_minus_2 ).quantize(Decimal('0.1'), rounding=ROUND_DOWN)

print(n_str_plus_1)
print(n_str_plus_2)
print(n_str_minus_1)
print(n_str_minus_2)

結果

1.2
1.8
-1.2
-1.8

上記プログラムは、小数点以下1桁目に丸めるようにプログラムしましたので、小数点以下1桁目より下で切り捨てられていることが確認できます。

②「負の無限大に近づける」切り捨て

経営計画などのシミュレーション時には、計算結果をネガティブな方に設定したいときもありますよね。

小数点以下を負の方向に切り捨てる方法を2種類ご紹介します。

math.floor()

mathモジュールのfloor()関数を用いると、小数点以下に値がある場合、マイナス方向に整数として出力します。

import math

n_floor_plus_1 = math.floor(1.25)
n_floor_plus_2 = math.floor(1.86)
n_floor_minus_1 = math.floor(-1.25)
n_floor_minus_2 = math.floor(-1.86)

print(n_floor_plus_1)
print(n_floor_plus_2)
print(n_floor_minus_1)
print(n_floor_minus_2)

結果

1
1
-2
-2

正の値と負の値がある場合は、混乱しやすくなりますので注意しましょう。

decimalモジュールのROUND_FLOORモード

任意の小数点以下で負の方向に切り捨てを行いたい場合は、decimalモジュールのROUND_FLOORを使います。

from decimal import Decimal, ROUND_FLOOR

n_float_plus_1 = 1.25
n_float_plus_2 = 1.86
n_float_minus_1 = -1.20
n_float_minus_2 = -1.86

n_str_plus_1 = str(n_float_plus_1)
n_str_plus_2 = str(n_float_plus_2)
n_str_minus_1 = str(n_float_minus_1)
n_str_minus_2 = str(n_float_minus_2)

n_str_plus_1 = Decimal( n_str_plus_1 ).quantize(Decimal('0.1'), rounding=ROUND_FLOOR)
n_str_plus_2 = Decimal( n_str_plus_2 ).quantize(Decimal('0.1'), rounding=ROUND_FLOOR)
n_str_minus_1 = Decimal( n_str_minus_1 ).quantize(Decimal('0.1'), rounding=ROUND_FLOOR)
n_str_minus_2 = Decimal( n_str_minus_2 ).quantize(Decimal('0.1'), rounding=ROUND_FLOOR)

print(n_str_plus_1)
print(n_str_plus_2)
print(n_str_minus_1)
print(n_str_minus_2)

結果

1.2
1.8
-1.2
-1.9

小数点以下1桁目で丸めるように設定しましたので、小数点以下2桁目がある場合は、小数点以下1桁目をマイナス方向に丸められました。

③「0から遠ざける」切り上げ

比率を用いた統計処理などを行う場合、正の値はプラス方向に丸め、負の値はマイナス方向に丸めたい場合があります。

そのようなときに使用するのがdecimalモジュールのROUND_UPです。

decimalモジュールのROUND_UPモード

decimalモジュール処理のroundingにROUND_UPを設定すると0から遠ざける丸め処理を実行できます。

from decimal import Decimal, ROUND_UP

n_float_plus_1 = 1.22
n_float_plus_2 = 1.86
n_float_minus_1 = -1.22
n_float_minus_2 = -1.82

n_str_plus_1 = str(n_float_plus_1)
n_str_plus_2 = str(n_float_plus_2)
n_str_minus_1 = str(n_float_minus_1)
n_str_minus_2 = str(n_float_minus_2)

n_str_plus_1 = Decimal( n_str_plus_1 ).quantize(Decimal('0.1'), rounding=ROUND_UP)
n_str_plus_2 = Decimal( n_str_plus_2 ).quantize(Decimal('0.1'), rounding=ROUND_UP)
n_str_minus_1 = Decimal( n_str_minus_1 ).quantize(Decimal('0.1'), rounding=ROUND_UP)
n_str_minus_2 = Decimal( n_str_minus_2 ).quantize(Decimal('0.1'), rounding=ROUND_UP)

print(n_str_plus_1)
print(n_str_plus_2)
print(n_str_minus_1)
print(n_str_minus_2)

結果

1.3
1.9
-1.3
-1.9

小数点以下1桁目で丸めるようにプログラムしていますので、小数点以下2桁目が0以上の場合は、小数点以下1桁目の値を変更します。


数値が正の場合は小数点以下1桁目に1が足され、負の値の場合は、小数点以下1桁目から1が引かれる、という処理です。

④「正の無限大に近づける」切り上げ

今度は、負の方向に丸めるROUND_FLOORとは逆の、正の方向に丸める処理について紹介します。

math.ceil()

正の方向に整数として丸める処理には、mathモジュールのceil()関数が便利です。

import math

n_ceil_plus_1 = math.ceil(1.22)
n_ceil_plus_2 = math.ceil(1.86)
n_ceil_minus_1 = math.ceil(-1.22)
n_ceil_minus_2 = math.ceil(-1.86)

print(n_ceil_plus_1)
print(n_ceil_plus_2)
print(n_ceil_minus_1)
print(n_ceil_minus_2)

結果

2
2
-1
-1

小数点以下1桁目が0以上の場合は、整数1桁目が正の方向に丸められます。

decimalモジュールのROUND_CEILINGモード

任意の桁数で正の方向に丸めたい場合は、decimalモジュールのROUND_CEILINGを使います。

from decimal import Decimal, ROUND_CEILING

n_float_plus_1 = 1.22
n_float_plus_2 = 1.86
n_float_minus_1 = -1.20
n_float_minus_2 = -1.89

n_str_plus_1 = str(n_float_plus_1)
n_str_plus_2 = str(n_float_plus_2)
n_str_minus_1 = str(n_float_minus_1)
n_str_minus_2 = str(n_float_minus_2)

n_str_plus_1 = Decimal( n_str_plus_1 ).quantize(Decimal('0.1'), rounding=ROUND_CEILING)
n_str_plus_2 = Decimal( n_str_plus_2 ).quantize(Decimal('0.1'), rounding=ROUND_CEILING)
n_str_minus_1 = Decimal( n_str_minus_1 ).quantize(Decimal('0.1'), rounding=ROUND_CEILING)
n_str_minus_2 = Decimal( n_str_minus_2 ).quantize(Decimal('0.1'), rounding=ROUND_CEILING)

print(n_str_plus_1)
print(n_str_plus_2)
print(n_str_minus_1)
print(n_str_minus_2)

結果

1.3
1.9
-1.2
-1.8

小数点以下1桁目で丸めるようにプログラムしましたので、小数点以下2桁目があり、正の値の場合は、小数点1桁目に1が足される結果になりました。負の値の小数点以下1桁目は、変わっていないことに注意しましょう。






この記事をシェア