読者です 読者をやめる 読者になる 読者になる

のた犬のうまい猫めし

どら猫が作る、のた犬のための飯、略称、どら飯について語りつつ、各種技術、経済系セミナーに参加した報告、OSSいじってみた等のネタを入れていきます。更新情報はtwitterの@nota_inuにて。

Java女子部セミナー「数値型おさらい&金勘定ことはじめ(BigDecimal入門、Money and Currency API紹介)」宮川拓氏

 

  • double、floatは計算が早い(コンピュータが使いやすい)型。近似値に丸められてしまう。二進の浮動小数点型。10進少数は各桁に10のn乗かけた数の和。10進の少数でぴったり表せる値を二進少数で表すと循環小数になる(無限に桁が続く)。double/floatは固定精度、有効桁数有限。
  • floatの32bitの中身:符号、指数(小数点の場所)、仮数(固定有限)。仮数のけたしか扱えない。0.3を二進にすると無限になり、doubleは有限なので近似値になる。(資料P.12)
  • double/floatで金勘定すると、intで定価1000円、7%引き(doubleで0.07)で売るとすると……。930円かと思いがちだが、929円になってしまう!! 値引き率がdoubleで0.07がすでに問題。集計(平均を見たい)とかリスク計算とか正確な金額がいらない場合以外は、BigDecimalクラスを使う必要がある。
  • BigDecimal:10進浮動小数点数。10進少数が正確に表せ、金勘定に使える。内部的には整数値+スケール。整数値:任意桁数の10進のけたを表すBigInteger。(メモリが許す限り桁数が取れる。)スケール:小数点を右からいくつ動かすかのint。12345+スケール6は0.012345.-3は12345000。ただマイナスで書くときはあまりない。
    12.345と12.34500は、数としては同じだが中身は違う。new BigDecimal("1.23")、BigDecimal.ValueOf(42L)のように定義。足し算、引き算は大きいほうのスケールに合わせて演算される。2.34+4.560=6.900(スケール3)。(スケールを合わせて整数値を足し合わせている。)掛け算ではスケールが足しあわされる。先程の例だと10.67040(スケール5)。
  • 端数処理:setScale(新しいスケール、丸めモード)。3でスケール2で丸めFLOORなら3.00.1.7320を2でCEILINGなら1.74。CEILINGは切り上げ。HALF_UPなら四捨五入。UNNECESSARYは丸めが必要ならArthmeticException。(丸めが必要になることはなく、あればバグ、を示す場合。)
  • round(MathContext)は最大精度を指定して丸めるので使えない。(整数値全体の桁数を指定する。)金勘定の場合はsetScale。1.234をroundで3指定だと1.23。56.789は56.7。0.0001は0.0001。
  • new BigDecimal(0.07)はdouble扱い。"0.07"にしないとダメ。バグとりツールとか使うとひっかけてくれたりする。(このままではせっかくBigDecimalで書いてもまた929円になる。要注意。)
  • 100と、33.3+66.7は、equalsで等しくならない。(スケールが異なるため。equalsはスケールが異なるとtrueにならない。)スケールを気にしないときにはcompareToを使う。(あるいはスケールを合わせて100を100.0にする。たぶんスケールを合わせたほうが良いのでは、とのこと。)
  • 割り算:ゼロ除算、割り切れない、桁数大きい、という問題があるためあまり使わないほうがよい。代わりに除数の逆数をかける。(ただし逆数出すのに割り算使ってしまうと同じ。初めから別ルートで逆数にするか、あきらめるか。)金勘定のための割り算は、divide(除数、スケール、丸めモード)。
  • 例:年間費用を12か月ごとに均等割りして、ひと月は小数点下2ケタまでに切り捨て、端数は最後の月に。→ 費用がマイナスの時には0の方向に丸めるか、-無限大の方向に丸めるか、が問題になる。(-0.123を-0.12か、-0.13か。)divide(BigDecimal.valueOf(12),2,RoundingMode.FLOOR)。subtract(月ごと費用.multiply(BigDecimal.valueOf(11)) ←プリミティブ型でないと、引き算、掛け算に、記号は使えない(コンパイルエラーになる)ので注意。
  • JavaSE9で、Money and Currency APIができる。通貨換算(換算用プロバイダにアクセスして換算。MonetaryConversions.getExchangeRateProvider().getCurrencyConversion(米ドル));。Moneta換算プロバイダはあまりよくないので、自前で作るのが良い。

 


質疑応答

  • 0.0001をスケール3「精度3(最大3ケタ)」(講師からご指摘いただきました。ありがとうございました&失礼しました)で丸めると0.0001になる理由は? →scaleは4。unscaledValue(スケールされていない整数値)は1。unscaledValueは1で丸める必要がないため元の数と同じになる。56.789は整数部分が56789で5ケタあるので、精度3だと多すぎるので56.7。多かったら切り捨てるが、少なくても追加するわけではない。
  • SQLServerなど、データベースの場合も同じことが起こる? → 仕様が二進数なら起こるだろう。(DBによって違うかも。)