使用Version:Python 3.11.2
結論
Pythonで四捨五入する際にはround関数ではなく Decimal.quantize() を利用する
round関数は四捨五入するのではなく偶数丸め(銀行丸め)をする
from decimal import * # あるいは from decimal import Decimal, ROUND_HALF_UP
# 121 が返る
Decimal("120.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)
# 120 が返る
round(120.5)
decimalモジュール
decimalモジュールは十進数を正確に表現するためのPython3標準ライブラリである
https://docs.python.org/ja/3/library/decimal.html
decimalモジュールで四捨五入するための手順
- Decimal型のオブジェクトを作る
-
Decimal(<文字列>)
で宣言する - 数値型を渡すと正しい精度で計算できないため必ず文字列型を渡すこと
-
- quantizeメソッドで四捨五入する
-
Decimal(<文字列>).quantize(有効桁、rounding=ROUND_HALF_UP)
- 有効桁も Decimal型オブジェクト で表現する
- ex. 一の桁までなら
Decimal("1")
- ex. 小数点第一位までなら
Decimal("0.1")
-
丸めモードにROUND_HALF_UP
を指定することで四捨五入する
切り上げや切り捨て処理に変更することも可能
https://docs.python.org/ja/3/library/decimal.html#rounding-modes
round関数
偶数丸め(銀行丸め)は結果の最終桁が偶数になるように丸め処理する
- 最終桁が
5
のとき四捨五入の結果と異なる場合がある - つまり数によっては四捨五入の結果と偶数丸めの結果が一致することもある
【0.1
から 1.0
を一の位まで丸める場合】
-
0.5
のときについて- Decimal.quantize() は
1
を返している- 正しく四捨五入できている
- round() は
0
を返している-
0
と1
のうち偶数である0
が選ばれる
-
- Decimal.quantize() は
【1.1
から 2.0
を一の位まで丸める場合】
-
1.5
のときについて- Decimal.quantize() は
2
を返している- 正しく四捨五入できている
- round() も
2
を返している-
1
と2
のうち偶数である2
が選ばれる
-
- Decimal.quantize() は
実験
様々な関数を用いて丸め処理を実行する
from decimal import *
import unittest
# 四捨五入する関数(有効桁ごとに用意)
my_round_a = lambda n:Decimal(str(n)).quantize(Decimal('1'), rounding=ROUND_HALF_UP)
my_round_b = lambda n:Decimal(str(n)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)
my_round_c = lambda n:Decimal(str(n)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
my_round_d = lambda n:Decimal(str(n)).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP)
# テストケースの作成
class testMyRound(unittest.TestCase):
def setUp(self):
pass
def test_round_1(self):
# 0.5
self.assertEqual(round(0.5), 0)
self.assertEqual(my_round_a(0.5), Decimal('1'))
# 1.5
self.assertEqual(round(1.5), 2)
self.assertEqual(my_round_a(1.5), Decimal('2'))
# 2.5
self.assertEqual(round(2.5), 2)
self.assertEqual(my_round_a(2.5), Decimal('3'))
def test_round_2(self):
# 1.5000を一の桁まで四捨五入
self.assertEqual(round(1.5000), 2)
self.assertEqual(round(1.5000, 0), 2.0)
self.assertEqual(my_round_a(1.5000), Decimal('2'))
# 1.4500を小数点第一位まで四捨五入
self.assertEqual(round(1.4500, 1), 1.4)
self.assertEqual(my_round_b(1.4500), Decimal('1.5'))
# 1.5450を小数点第二位まで四捨五入
self.assertEqual(round(1.5450, 2), 1.54)
self.assertEqual(my_round_c(1.5450), Decimal('1.55'))
# 1.5545を小数点第三位まで四捨五入
self.assertEqual(round(1.5545, 3), 1.554)
self.assertEqual(my_round_d(1.5545), Decimal('1.555'))
def test_round_3(self):
# 2.5000を一の桁まで四捨五入
self.assertEqual(round(2.5000), 2)
self.assertEqual(round(2.5000, 0), 2.0)
self.assertEqual(my_round_a(2.5000), Decimal('3'))
# 2.5500を小数点第一位まで四捨五入
self.assertEqual(round(2.5500, 1), 2.5)
self.assertEqual(my_round_b(2.5500), Decimal('2.6'))
# 2.5550を小数点第二位まで四捨五入
self.assertEqual(round(2.5550, 2), 2.56)
self.assertEqual(my_round_c(2.5550), Decimal('2.56'))
# 2.5555を小数点第三位まで四捨五入
self.assertEqual(round(2.5555, 3), 2.555)
self.assertEqual(my_round_d(2.5555), Decimal('2.556'))
def tearDown(self):
pass
if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(testMyRound))
unittest.TextTestRunner(verbosity=2).run(suite)
test_round_1 (__main__.testMyRound) ... ok
test_round_2 (__main__.testMyRound) ... ok
test_round_3 (__main__.testMyRound) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
テストケースtest_round_3ではround関数で正しく偶数丸めできていない
その原因はfloatが浮動小数点数を正確に表せないことにある
https://docs.python.org/ja/3/library/functions.html?highlight=round#round
浮動小数点数に対する round() の振る舞いは意外なものかもしれません: 例えば、 round(2.675, 2) は予想通りの 2.68 ではなく 2.67 を与えます。これはバグではありません: これはほとんどの小数が浮動小数点数で正確に表せないことの結果です。
decimalモジュールを使わない方法
Python3の組み込み関数のみでも四捨五入することができる
(コメント欄にて情報いただきました。ありがとうございます。)
# 書き方その1
"""
Args:
x: 四捨五入対象の値
n: 桁の指定(round関数と同様)
"""
round2 = lambda x,n=0:(1 if x >= 0 else -1) * int(abs(x) * 10**n + 0.5)/10**n
# 書き方その2
def round2(x,n=0):
if x>=0:
int(x * 10**n + 0.5)/10**n
else
int(x * 10**n - 0.5)/10**n
負の数の四捨五入では一度絶対値について四捨五入してから符号を付ける
(JIS Z 8401規格にて定義されている)
- 絶対値を取得する
- 10の累乗倍する
- 0.5を足す
- int関数で端数を切り捨てする
- 10の累乗倍で割る
- 適切な符号を付ける