こんにちは!フリーランスエンジニア・ライターの平山です。
Pythonで小数点を扱っていると、
普通に計算をしたはずなのに、結果がおかしい・・・
といった場面に遭遇したこともあるのではないでしょうか? 小数点は様々な場面で使うので、細かいところでも引っかかるとストレスですよね。
そこで、この記事では
- 小数点の基礎
- 小数点の操作(切り上げ、切り捨て、四捨五入・・・)
といった基本的な内容から、
- decimalモジュールを使った正確な計算
という応用まで、詳しく解説していきます。これを読んで小数点をマスターしましょう!
本記事を読む前に、Pythonがどんなプログラミング言語なのかをおさらいしておきたい人は次の記事を参考にしてください。
→ Pythonとは?特徴やできること、活用例をわかりやすく簡単に解説
なお、その他のPythonの記事についてはこちらにまとめています。
小数点(float型)の基礎
まずは、Pythonにおける小数点の基礎を確認しましょう。Pythonは数値型として、整数型、小数型、複素数型の3つを持ちます。そして、それぞれにint型、float型、complex型が対応します。この3つの型を使って数字を表現します。
他の言語を学んだ経験のあるかたには「倍精度小数点は?」と思われるかもしれません。倍精度小数点に関してはデフォルトで倍精度と同等の精度を持っているのでご安心ください。Python初学者の方やスクリプト言語しか経験のない方にとっては「型」という概念自体、あまり馴染みがないかもしれません。
型とは、変数に代入できるものを制限する規則です。浮動小数点型には浮動小数点以外代入できない、という感じですね。普段はPythonが自動的に変数の型を判別してくれるため、あまり意識しないでプログラミングをしている方も多いでしょう。
ですが、型が合わなくて変なエラーが出た、というのはありがちなハマりネタです。そのため、型に対する理解をきちんと持っておく事は意外と重要です。
今回はfloat型についてのみですが、これを機に型について意識してみましょう。それでは続いて小数点の実際の操作を見ていきます。
小数点数の操作
ここからはPythonでの実際の小数点の操作をソースとともに見ていきましょう。
切り上げ
まずは、切り上げです。切り上げにはmathモジュールのceil()を使います。以下の例をご覧ください。
import math float_var = 1.234 print(math.ceil(float_var)) # 結果 2
任意桁での切り上げにはdecimalモジュールが必要となります。decimalモジュールは3章で詳しく説明します。
切り捨て
続いて切り捨てです。
切り上げと同様に、mathモジュール内にfloorというメソッドが用意されています。
import math float_var = 1.234 print(math.floor(float_var)) # 結果 1
また、割り算によってfloat型が生成される場合があります。この時は、次のように演算子を工夫する事で切り上げと似たような効果が得られます。
int_var1 = 8 int_var2 = 5 float_answer = int_var1/int_var2 int_answer = int_var1//int_var2 print(float_answer) print(int_answer) # 結果 1.6 1
こちらは何をしているのかというと、割り算の商、つまり整数部分のみを返す、という演算をしています。他言語では別途メソッドを用意して対応することが多い演算ですが、Pythonでは組み込みの演算子のみで実現できます。
覚えておくとなかなか便利な機能ですね。
四捨五入
つぎは四捨五入です。四捨五入についてはPython特有のハマりポイントがあるので、気を引き締めていきましょう。Python3で次のコードを実行してみてください。
float_n = 10.5 float_m = 11.5 print(round(float_n)) print(round(float_m)) # 結果 10 12
どうでしょうか?多くの方はround(float_n) = 11 , round(float_m) = 12と予想しませんでしたか?他の言語でround()はほぼ四捨五入であるため、他言語経験者ほど不思議な気分になることでしょう。
実はPythonのround()は四捨五入ではありません! round()は偶数への丸め、という処理をする関数なのです。偶数への丸めの定義は以下の通りです。
端数が0.5より小さいなら切り捨て、端数が0.5より大きいならは切り上げ、端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める。
出典:Wikipedia
なんでこんな面倒な処理をしてるの!?と頭を抱えたくなりますよね。ですが、Python公式によると
これはバグではありません: これはほとんどの小数が浮動小数点数で正確に表せないことの結果です。
https://docs.python.jp/3/library/functions.html#round”
https://docs.Python.org/ja/3.7/tutorial/floatingpoint.html
round()は慣れるまでは色々と扱いにくいかもしれません。ですが、気楽に任意の桁を丸めることのできる組み込み関数でもあります。桁を指定して丸めるには下のように書きます。
print(round(1.234,2)) # >>>1.23
- 第1引数に丸めたい小数
- 第2引数に丸めたい桁
一般的な四捨五入とは挙動が異なる、と覚えておけば気楽に桁を丸めることができますね。
桁数の取得
こんどはfloat型の桁数を取得する方法について見てみましょう。floatは残念ながら関数一つで桁数を取得する方法がありません。割り切れない数が変数に入っていることがあるため、桁数が無限とかが良くあります。
そのため、桁数を求める関数が定義されていないんですね。他の型には使えるlen()がfloat型には使えません。なので、float型で桁数を取得したい場合、一工夫が必要です。さっそくソースを見てみましょう。
float_var = 14.25 float_var -= int(float_var) print(len(str(float_var))-2)
ソースの解説をします。
- 元の数から整数部分を取り除きます
- floatを文字列に変換します
- 文字列”0.25″から0.を取り除いた残りの長さを表示します
これで小数点以下の桁数を求められました!
Eを小数点表示にしたい
次は表示の変換方法について学びましょう。
Pythonのfloat型は
- 桁数が小さい時は普通の表記
- 桁数が大きくなると指数表記
と自動で切り替わるようになっています。ですが、処理や見た目の問題で指数表示したくない場合もあります。そんな時に使えるのが組み込み関数のformat()。
float = 1.23e+16 print(format(float, "f"))
使い方は第1引数に変換したい数、第2引数に文字列のfを入れるだけです。逆に長い小数を指数表示したい場合は第2引数をeに変えれば実現できます。
format()は他にもたくさんの使い方があります。気になる方はこちらの記事もチェックしてみましょう。
次の章では、小数を扱っているときに遭遇しやすい問題を解決していきます。
小数(float型)にまつわる問題
足し算したらおかしい
ここからはPythonの小数にまつわる良くある問題に焦点を当てていきましょう。まず、次のソースを実行してみてください。
print(0.1+0.1+0.1) # >>>0.30000000000000004
算数的にはどうみてもおかしいですよね。ですが、この問題はfloatが内部で2進数を使っている以上、実はどうしようもないんです。誤差としても1京分の1程度なので、多くの場合、実用上は問題ないでしょう。
どうしても誤差の発生が許されない場合、次章で紹介するdecimalモジュールを使います。もしくは、次のソースのように必要な桁数で丸めてしまうのもアリですね。
print(round(0.1+0.1+0.1,3)) # >>>0.3
int型に変換するには?
続いて型変換についてです。小数を整数型に変換するにはint()を使います。
float_var=1.234 int_var = int(float_var) print(int_var)
また、前章で紹介した
- 切り上げ
- 切り捨て
- round()
は返り値が整数型に変換されます。なお、int()の挙動は切り捨てと同じになります。状況によって使い分けると良いですね。
他の型から小数型に変換するには
こんどはint型から小数型に変換する場合を考えてみましょう。基本的に、int型とfloat型が混在した計算をすると、結果はfloat型になります。こういったシンプルな型操作はPythonの側で自動的にやってくれます。
ただ、明示的に型変換をしたい場合もあります。例えば、文字列の中にある数字を取り出したい場合ですね。その時はこのように、float()を使って文字列型から小数型に変換してあげましょう。
float_var = float("1.234") # >>>1.234 float型に変換される
decimalモジュールと組み合わせる
この章ではdecimalモジュールと組み合わせて、小数型をより便利に使っていく方法をみていきましょう。
decimalモジュールの基礎
まずは、decimalモジュールの基礎を学びます。decimalモジュールはどういうものかというと・・・
decimal モジュールは正確に丸められた十進浮動小数点算術をサポートします。
https://docs.Python.jp/3/library/decimal.html
かみ砕いて言うと、Pythonで10進数がかなり正確に扱えるようになるよ、ということです。前の方でちらりと言いましたが、float型は内部では2進数で扱われています。
そのため、人間にわかりやすい10進数に変換すると、ときどき訳のわからない(ように見える)結果を返してくれる訳です。それが、このdecimalモジュールを使うことで、おおよそ改善できるのです。
decimalモジュールの使い方
さっそくdecimalモジュールを使ってみましょう。
from decimal import * getcontext().prec = 16 dec_var = Decimal("0.1")+Decimal("0.1")+Decimal("0.1") print(dec_var)
使い方は
- まずdecimalモジュールをインポートする
Decimalオブジェクトをなんども使うのでfrom importとすると楽チン - 有効桁数を設定する
- 数値はDecimalオブジェクトに文字列として代入する
さて、実際に上のコードを動かしてみると、
0.3
という結果が得られました!前回のように変な誤差はつきませんでしたね。ここで、注意点が1つあります。
Decimalオブジェクトに代入された数字はDecimal型になります。Decimal型はint型とは普通に計算できますが、float型とは計算できません。Decimal型を使う場合、全ての小数をdecimal型で統一しましょう。
decimal モジュールの注意点
先ほどの例で、「数字を扱うのになんで文字列で代入してるんだろう?」と疑問に思った方はいないでしょうか?実はこれ、decimalモジュールで引っかかりやすいポイントなんです。
ぜひ注意して覚えておいてください。試しに、次のソースで実験してみましょう。
print(Decimal(0.1))
結果はこのようになります。
Decimal('0.1000000000000000055511151231257827021181583404541015625')
何やらよくわからない数値が出てきましたね。実はこれ、float型の数値が実際にどのような数値として扱われているのかを示しているんです。同じことを何度も書いていますが、コンピューター内部では2進数で物事が扱われています。
そのため、2進数で正確に表現できない数は近似値を使うしかありません。10進数で表現すればたった1桁の0.1も2進数では正確に表現できず、このような近似値を使わざるを得ないのです。
だから、0.1+0.1+0.1 = 0.30000000000000004という謎の計算が成り立っていたわけですね。Decimalオブジェクトに代入するのは、float型の0.1ではダメなのです。
そのために、文字列の”0.1″という一見回りくどいものを代入しているんですね。Decimalの代入は文字列! と覚えましょう。金融や会計系のシステムを組む場合のように、特に誤差が絶対に許されない場面も存在します。うっかりfloat型を代入して大惨事、なんてことにならないようにしましょう。
任意桁の切り上げ、切り下げ
decimalモジュールの便利なメソッドにquantize()があります。任意の桁で好きな形式の丸めを行えるメソッドですので、こちらも使えるようになりましょう。
使い方の例です
Decimal('7.325').quantize(Decimal('.01'), rounding=ROUND_DOWN)
結果
Decimal('7.32')
Decimalオブジェクトに対してquantizeメソッドを使います。
- 第1引数で整形したい形のDecimalオブジェクト
- 第2引数で丸めの形式(省略可能)
この場合は小数第2位までで切り捨てられました。第2引数の丸め形式は色々なオプションがあります。
場合によって使い分けられて便利ですが、基本的に
- 切り捨て:ROUND_DOWN
- 切り上げ:ROUND_UP
- Python風のround:ROUND_HALF_EVEN
- 一般的な四捨五入:ROUND_HALF_UP
を覚えておけば十分でしょう。公式の丸めオプション一覧はこちら
https://docs.Python.jp/3/library/decimal.html#rounding-modes
小数点の操作を完全理解!まとめ
いかがだったでしょうか?
この記事では
- Pythonのfloat型の基礎
- 切り捨て、切り上げの各種操作
- float型を使う際の注意点
- 10進数を正確に扱えるdecimal型について
といったことを学びました。これで、Pythonの小数を扱う際に遭遇する基本的な問題は解決できるようになりました。小数点に関して悩むことがあったら、またこの記事を読み直してみてください!