最近Javaで書かれたバッチをPythonに書き換えている。
Javaで正確な丸めを行うためにBigDecimal
を使ったコードがある。
それをPythonに書き換える際に対応するメソッドがなく困った。
本記事では対応策をまとめる。
Java側
Javaのコードが以下のようだとする。
import java.math.BigDecimal;
import java.math.RoundingMode;
class BigDecimalDivideTest {
public static void main(String[] args) {
BigDecimal someValue = BigDecimal.valueOf(3215);
BigDecimal BD_100 = BigDecimal.valueOf(1000);
int SCALE_2 = 2;
System.out.println(someValue.divide(BD_100, SCALE_2, RoundingMode.HALF_UP));
// 3.22が表示される
}
}
Python側
from decimal import *
some_value = Decimal('3215')
bd_100 = Decimal('1000')
scale_2 = 2
divided_value = some_value / bd_100
scaled_decimal = Decimal(str(10 ** - scale_2)) # => Decimal('0.01')になる。
# 丸め処理にはquantizeメソッドを使う
divided_value.quantize(scaled_decimal, rounding=ROUND_HALF_UP)
# => Decimal('3.22')
説明
Pythonにおける、JavaのBigDecimal
に対応するライブラリはdecimal
である。
だが decimal
の divide
メソッドでは、Javaのようにスケールや丸めについて引数を取るものがない。
なので、スケール処理と丸めはquantize
で割り算のあとに行う。
なお、quantize
でエラーが出る場合がある。
from decimal import *
getcontext().prec = 2 # 追加
some_value = Decimal('3215')
bd_100 = Decimal('1000')
scale_2 = 2
divided_value = some_value / bd_100
scaled_decimal = Decimal(str(10 ** - scale_2))
divided_value.quantize(scaled_decimal, rounding=ROUND_HALF_UP)
# => decimal.InvalidOperation 例外が発生する
他の操作と違い、打ち切り(quantize)操作後の係数の長さが精度を越えた場合には、 InvalidOperation がシグナルされます。
getcontext().prec = 2
を追加した上でquantizeを行うと、quantize結果が数値全体で2桁(3.2とか)でないとエラーになる。
今回はquantize結果は3.22であり結果が3桁になるので、InvalidOperationが出る。
なんで書こうと思ったか
Pythonのドキュメントの一番上で以下のようなコードが書いてある。
from decimal import *
getcontext().prec = 6
Decimal(1) / Decimal(7)
# => Decimal('0.142857')
これを見て、私は桁数を指定するにはgetcontext().prec
を指定すればいいのかとミスリードした。
precは数値全体の桁数の話なので、prec = 6としても小数点以下6桁を保証してくれるわけではない。
例えば上記の割り算の対象数字が大きかったとすると、以下のようになる。
from decimal import *
getcontext().prec = 6
print(Decimal(10000000) / Decimal(7))
# => Decimal('1.42857E+6')
print(float(Decimal(10000000) / Decimal(7)))
# floatに変換すると1428570.0
# prec = 有効桁数は6なので、142857までの6桁までは計算結果が保持されるがそれ以降は0に
参考
-
なぜBigDecimalを使わなければならないのか | Java好き
- BigDecimalを使う理由が理解できた。