Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

金勘定のためのBigDecimalそしてMoney and Currency API

9,553 views

Published on

BigDecimal for money counting / Money and Currency API

Published in: Technology
  • 本資料はCC Attributionライセンスで公開します。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

金勘定のためのBigDecimalそしてMoney and Currency API

  1. 1. 金勘定のためのBigDecimal そしてMoney and Currency API 2015-06-11 Java女子部勉強会 ハッシュタグ: #javajo 宮川 拓
  2. 2.  @miyakawa_taku  JJUG幹事  SI屋で賃労働  オレオレJVM言語Kinkを作っています https://bitbucket.org/kink/kink  尾上部屋の里山関のファンです 自己紹介 #javajo 2/57
  3. 3. みたいな金勘定を考えます ペンキ赤 321円 × 1.3リ ッ ト ル = 417.3円 ペンキ青 432円 × 2.2リ ッ ト ル = 950.4円 ペンキ緑 543円 × 1.6リ ッ ト ル = 868.8円 小計( 端 数 切 り 捨 て ) 2,236円 消費税( 四 捨 五 入 ) 179円 合計 2,415円 テーマ: 金勘定 小数の 演算 端数 処理 #javajo 3/57
  4. 4. セッション内容 double/floatで金勘定してはいけない BigDecimalで金勘定しよう 先取りMoney and Currency API #javajo 4/57
  5. 5. double/floatは固定精度の 2進浮動小数点数型です 10進小数が正確に表せないので、 金勘定には使えません 基本的には統計・科学技術計算のための データ型です double/float 「精度」 =「有効桁数」 #javajo 5/57
  6. 6. ありがちな言い訳  キリの良い金額しか使わないし……  誤差が出るような計算しないし……  精度足りてるからdouble/floatで大丈夫! ナーンセンス! double/float #javajo 6/57
  7. 7. double/floatではダメな理由 金額の10進小数 double/float 2進の浮動小数点数型 近似値に 丸められてしまう 以下、なぜこうなるのか説明します #javajo 7/57
  8. 8. 10進小数は 各桁に10nを掛けた数の和を表します たとえば10進の12.625: そもそも10進小数とは? 1 2 . 6 2 5 × × × × × 10 (101) 1 (100) 1/10 (10-1) 1/100 (10-2) 1/1000 (10-3) #javajo 8/57
  9. 9. 同じように2進小数は 各桁に2nを掛けた数の和を表します たとえば2進の1100.101: では2進小数とは? 1 1 0 0 . 1 0 1 × × × × × × × 8 (23) 4 (22) 2 (21) 1 (20) 1/2 (2-1) 1/4 (2-2) 1/8 (2-3) #javajo 9/57
  10. 10. 10進小数→2進小数 10進小数でピッタリ表せる値を 2進小数で表そうとすると、一般に 無限に桁が続く循環小数になります 0.1 = 0.0 0011 0011 0011 0011 0011 00... 0.2 = 0. 0011 0011 0011 0011 0011 001... 0.3 = 0.0 1001 1001 1001 1001 1001 10... #javajo 10/57
  11. 11. double/floatは固定精度、 つまり有効桁数は有限です floatの32ビットの中身: double/floatの桁数は有限 01000000110010100000000000000000 符号 0:正 1:負 指数 小数点の場所 仮数 2進の桁 桁数は固定で有限 #javajo 11/57
  12. 12. #javajo10進小数→double/float 無限の桁を有限桁に押し込もうとすると 10進小数 0.3 2進小数(無限桁数) 0.01001100110011... double/float(有限桁数) 0.010011001...110011 近似値に 丸められる 12/57
  13. 13. double/floatで金勘定すると 何が起きるのでしょうか? #javajo 13/57
  14. 14. double/floatで金勘定すると... int 定価 = 1000; double 値引率 = 0.07; int 売値 = (int) (定価 * (1 - 値引率)); System.out.println(売値); 😬 #javajo ?929 14/57
  15. 15. double/floatまとめ double/floatで金勘定してはいけません 例外:  集計・サマリ  証券のリスク計算  そのほか正確な金額がいらない場合 正確な金勘定をするためには BigDecimalクラスを使いましょう #javajo 15/57
  16. 16. セッション内容 double/floatで金勘定してはいけない BigDecimalで金勘定しよう 先取りMoney and Currency API #javajo 16/57
  17. 17. BigDecimal BigDecimal: 10進浮動小数点数 10進小数が正確に表せます 金勘定に使えます #javajo 17/57
  18. 18. BigDecimalで金勘定 int 定価 = 1000; BigDecimal 値引率 = new BigDecimal("0.07"); int 売値 = BigDecimal.ONE.subtract(値引率) .multiply(BigDecimal.valueOf(定価)) .setScale(0, RoundingMode.FLOOR) .intValue(); System.out.println(売値); 930 😄 #javajo 18/57
  19. 19. BigDecimalの構成 BigDecimal: (整数値, スケール) 整数値 任意桁数の10進の桁を表す BigInteger値 スケール 小数点を右端からいくつ左に 動かすかを表すint値 1 2 3 4 5 スケール:3 整数値: 12345 #javajo 19/57
  20. 20. ?12345000 BigDecimalの例 整数値 スケール 表される値 12345 0 12345 12345 3 12.345 12345 6 0.012345 12345 -3 #javajo スケール≧0の時、 スケールは小数点以下の桁数です 20/57
  21. 21. BigDecimalの例 同じ数を表す(整数値, スケール)の 組み合わせは複数存在します 整数値 スケール 表される値 12345 3 12.345 1234500 5 12.34500 #javajo 同じ数 21/57
  22. 22. #javajoBigDecimalの生成 主にnew BigDecimal(String)や valueOf(long)で値を生成します new BigDecimal("1.23") new BigDecimal("1.2300") BigDecimal.valueOf(42L) 1.23 スケール 2 1.2300 スケール 4 42 スケール 0 22/57
  23. 23. #javajoBigDecimalの演算 演算メソッドはオブジェクトを変更せず 結果となる新しい値を返します BigDecimal x = new BigDecimal("2.34"); BigDecimal y = new BigDecimal("4.560"); println(x.add(y)); println(x.multiply(y)); 6.900 10.67040 println(x); 2.34(変わらず) 和: 積: 23/57
  24. 24. BigDecimalの演算 足し算・引き算では、大きい方の スケールに合わせて演算されます 2.34 (スケール2) 4.560 (スケール3) + 6.900 (スケール3) #javajo 24/57
  25. 25. BigDecimalの演算 掛け算では、 スケールが足し合わされます 2.34 (スケール2) 4.560 (スケール3) × 10.67040 (スケール5) #javajo 25/57
  26. 26. BigDecimalの丸め 金額の端数処理の例:  金額の小数部を四捨五入  小数点以下2桁を残して切り捨て 端数処理に使うメソッド: setScale(新しいスケール, 丸めモード) #javajo BigDecimal 丸められた数 = 元の数.setScale(2, RoundingMode.FLOOR); 例: オブジェクトは 変更されません 26/57
  27. 27. #javajoBigDecimalの丸め setScale()による丸め: 3 2 FLOOR 3.00 1.7320 2 FLOOR 1.73 1.7320 2 CEILING 1.74 元の数 スケール 丸め 結果 27/57
  28. 28. BigDecimalの丸め 丸めモード: CEILING / FLOOR 切り上げ / 切り捨て UP / DOWN 0から遠い方 / 近い方 HALF_UP 四捨五入 HALF_DOWN 五捨五超入 HALF_EVEN 最近接偶数への丸め UNNECESSARY 丸めが必要になってしまったら ArithmeticException #javajo 28/57
  29. 29. × round(MathContext) round(MathContext)メソッドは 金勘定の端数処理には使えません #javajo setScale スケール(小数点以下の桁数)を 指定して丸める round 最大精度(整数値全体の桁数)を 指定して丸める round は、演算を繰り返すときに精度が膨れ上がって メモリを食い潰さないようにするためのメソッドです 29/57
  30. 30. × round(MathContext) 例: 精度3で切り捨てる MathContext mc = new MathContext(3, RoundingMode.FLOOR); ふつう、金 勘定でこんな処理はしないはずです #javajo new BigDecimal("1.234").round(mc) new BigDecimal("56.789").round(mc) new BigDecimal("0.0001").round(mc) 1.23 56.7 0.0001 30/57
  31. 31. BigDecimalの落とし穴 #javajo 31/57
  32. 32. BigDecimalの落とし穴1 int 定価 = 1000; BigDecimal 値引率 = new BigDecimal(0.07); int 売値 = BigDecimal.ONE.subtract(値引率) .multiply(BigDecimal.valueOf(定価)) .setScale(0, RoundingMode.FLOOR) .intValue(); System.out.println(売値); 😬 double! #javajo ?929 32/57
  33. 33. BigDecimalの落とし穴1 BigDecimalの生成にdoubleを使っては なにもかも台無しです new BigDecimal(String)を使いましょう #javajo 33/57
  34. 34. BigDecimalの落とし穴1: 修正 int 定価 = 1000; BigDecimal 値引率 = new BigDecimal("0.07"); int 売値 = BigDecimal.ONE.subtract(値引率) .multiply(BigDecimal.valueOf(定価)) .setScale(0, RoundingMode.FLOOR) .intValue(); System.out.println(売値); 930 😄 文字列を 渡しましょう #javajo 34/57
  35. 35. BigDecimalの落とし穴2 BigDecimal 総資産 = new BigDecimal("100"); BigDecimal 負債 = new BigDecimal("33.3"); BigDecimal 純資産 = new BigDecimal("66.7"); System.out.println( 総資産.equals(負債.add(純資産))); 😬 スケールが 異なるequalsが 使われている #javajo ?false 35/57
  36. 36. #javajoBigDecimalの落とし穴2 スケールが違うとequalsは成立しません 33.3 + 66.7 new BigDecimal("100") 100.0 (スケール1) 100 (スケール0) ! equals 36/57
  37. 37. BigDecimalの落とし穴2 BigDecimal.equalsはスケールも 比較します スケールを気にしないときは compareToを使いましょう #javajo 37/57
  38. 38. BigDecimalの落とし穴2: 修正a BigDecimal 総資産=new BigDecimal("100.0"); BigDecimal 負債 = new BigDecimal("33.3"); BigDecimal 純資産 = new BigDecimal("66.7"); System.out.println( 総資産.equals(負債.add(純資産))); true スケールを 合わせる 😄 または... #javajo 38/57
  39. 39. BigDecimalの落とし穴2: 修正b BigDecimal 総資産=new BigDecimal("100"); BigDecimal 負債 = new BigDecimal("33.3"); BigDecimal 純資産 = new BigDecimal("66.7"); System.out.println( 総資産.compareTo(負債.add(純資産) == 0)); true 😄 compareToを 使う #javajo 39/57
  40. 40. BigDecimalまとめ  BigDecimalは整数値とスケールで 構成されます  new BigDecimal(String)で定数を 作りましょう  端数はsetScaleで丸めましょう  BigDecimal.equalsはスケールも 比較対象です #javajo 40/57
  41. 41. 追補: BigDecimalの割り算 #javajo 41/57
  42. 42. #javajo割り算 割り算はフクザツ  ゼロ除算  割り切れないことがある  結果の桁数が大きくなるかも 例: 3/64 = 0.046875 可能なら逆数を掛ける方が良いです 例: numを4で割る num.mupltiply(new BigDecimal("0.25")) 42/57
  43. 43. #javajoBigDecimalの割り算 金勘定のための割り算メソッド: divide(除数, スケール, 丸めモード)  指定したスケールで結果を戻す  割り切れない時は丸める  結果が指定したスケールに収まらない ときは丸める divideと名のつく他のメソッドは罠です 43/57
  44. 44. 例: 費用配賦 #javajo 年間の費用を12ヶ月ごとに均等割します ただし、ひと月の費用は小数点下2桁までに 切り捨てて、端数は最後の月に寄せます 年間の費用を12ヶ月ごとに均等割します ただし、ひと月の費用は小数点下2桁までに 切り捨てて、端数は最後の月に寄せます 注意! 費用がマイナスの時は?  0の方向に丸める: -0.123 → -0.12  -∞の方向に丸める: -0.123 → -0.13 ここでは-∞の方向に丸めることにします 44/57
  45. 45. 例: 費用配賦 #javajo BigDecimal 年間費用 = new BigDecimal( "49.00" ); BigDecimal 月ごと費用 = 年間費用.divide( BigDecimal.valueOf(12), 2, RoundingMode.FLOOR); BigDecimal 最終月費用 = 年間費用.subtract( 月ごと費用.multiply(BigDecimal.valueOf(11))); System.out.println(月ごと費用); System.out.println(最終月費用); 年間の費用を12ヶ月ごとに均等割します ただし、ひと月の費用は小数点下2桁までに 切り捨てて、端数は最後の月に寄せます 4.08 4.12 45/57
  46. 46. セッション内容 double/floatで金勘定してはいけない BigDecimalで金勘定しよう 先取りMoney and Currency API #javajo 46/57
  47. 47. Money and Currency API JSR 354 Money and Currency APIは、 金勘定に関するAPIです  通貨、金額、丸め、通貨換算、書式化… Java SE 9で採用予定です クレディ・スイスが主導しています #javajo 47/57
  48. 48. なぜクレディ・スイス? スイスの特殊条件が影響?  周りは全部ユーロ圏  決済用代替通貨 WIRフラン, WIRユーロが流通している  現金決済の最小単位は 5ラッペン(5/100スイスフラン)  現金以外の最小単位は1ラッペン たしかに標準APIが欲しくなりそうです #javajo 48/57
  49. 49. JSRのステータス 2012-01 JSR Review 2012-02 Expert Group組成 2013-01 Spec Lead交代 2013-03 Early Draft 2013-10 Public Review 2014-03 Public Review 2 2015-03 Proposed Final Draft 2015-05 Final Release #javajo そこそこ難産 49/57
  50. 50. 構成 API: javax.money 実装 通貨 インタフェース 金額 インタフェース 丸め インタフェース SPI SPI SPI 通貨実装の プロバイダ 金額実装の プロバイダ 丸め実装の プロバイダ 実装はAPIから独立しています(参照実装: Moneta) 複数実装を組み合わせて使うことも可能です #javajo 50/57
  51. 51. 通貨: CurrencyUnit CurrencyUnit 日本円 = Monetary .getCurrency("JPY"); 通貨コード に対応する通貨 CurrencyUnit 地域の通貨 = Monetary .getCurrency(Locale.getDefault()); ロケールに 対応する通貨 #javajo 51/57
  52. 52. 金額: MonetaryAmount Monetaが提供する金額実装:  Money: 任意スケールの金額  FastMoney: 固定スケール(5)の金額  RoundedMoney: 演算ごとに丸める金額 Money 定価 = Money.of(1000, 日本円); System.out.println(定価); #javajo JPY 1000 52/57
  53. 53. 丸め: MonetaryRounding MonetaryRounding 整数に切捨 = Monetary .getRounding(RoundingQueryBuilder.of() .setScale(0) .set(RoundingMode.FLOOR) .build()); Money 売上 = Money.of( new BigDecimal("42.3"), "JPY"); Money 請求額 = 売上.with(整数に切捨); System.out.println(請求額); 結果のスケール 丸め方 #javajo JPY 42 53/57
  54. 54. 通貨換算: CurrencyConversion CurrencyConversion toUSD = MonetaryConversions .getExchangeRateProvider() .getCurrencyConversion(米ドル); Money 金額JPY = Money.of(42, 日本円); Money 金額USD = 金額JPY.with(toUSD); System.out.println(金額USD); 米ドルへの換算 Moneta の換算レートプロバイダは ECB/IMFのオンラインの 換算表を使います 通常、自前で換算レートプロバイダを実装するべきでしょう #javajo USD 0.3509... 54/57
  55. 55. Money and Currency APIまとめ 結構な重量級APIです つかいでのありそうなシステム  扱う通貨が可変  複数通貨間の換算を行う 関連情報へのリンク集: https://java.net/projects/jjug/pages/Adopt-a-JSR-JavaSE9 #javajo 55/57
  56. 56. セッション内容 double/floatで金勘定してはいけない BigDecimalで金勘定しよう 先取りMoney and Currency API #javajo 56/57
  57. 57. #javajo改版履歴 2015-04-11 JJUG CCC 2015 Spring 2015-06-11 Java 女 子 部 勉 強 会 「 数 値 型 お さ ら い & 金 勘 定 こ と は じ め ( BigDecimal 入 門 、 Money and Currency API 紹 介 ) 」 • BigDecimal の 割 り 算 を 追 補 • JSR-354 の 仕 様 変 更 に 追 随 • JSR-354 が Final Release さ れ た こ と を 反 映 2015-06-14 微 修 正 57/57

×