Upload
taku-miyakawa
View
3.659
Download
2
Embed Size (px)
Citation preview
@miyakawa_taku
JJUG幹事
SI屋で賃労働
オレオレJVM言語Kinkを作っています
https://bitbucket.org/kink/kink
尾上部屋の里山関のファンです
自己紹介 #javajo
2/57
みたいな金勘定を考えます
ペンキ赤 321円 × 1.3リットル = 417.3円
ペンキ青 432円 × 2.2リットル = 950.4円
ペンキ緑 543円 × 1.6リットル = 868.8円
小計(端数切り捨て) 2,236円
消費税(四捨五入) 179円
合計 2,415円
テーマ: 金勘定
小数の
演算
端数
処理
#javajo
3/57
double/floatは固定精度の
2進浮動小数点数型です
10進小数が正確に表せないので、
金勘定には使えません
基本的には統計・科学技術計算のための
データ型です
double/float
「精度」
=「有効桁数」
#javajo
5/57
ありがちな言い訳
キリの良い金額しか使わないし……
誤差が出るような計算しないし……
精度足りてるからdouble/floatで大丈夫!
ナーンセンス!
double/float #javajo
6/57
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
同じように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進小数→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
double/floatは固定精度、
つまり有効桁数は有限です
floatの32ビットの中身:
double/floatの桁数は有限
01000000110010100000000000000000
符号
0:正 1:負
指数
小数点の場所
仮数 2進の桁
桁数は固定で有限
#javajo
11/57
#javajo10進小数→double/float
無限の桁を有限桁に押し込もうとすると
10進小数 0.3
2進小数(無限桁数)
0.01001100110011...
double/float(有限桁数)
0.010011001...110011
近似値に丸められる
12/57
double/floatで金勘定すると...
int 定価 = 1000;
double 値引率 = 0.07;
int 売値 = (int) (定価 * (1 - 値引率));
System.out.println(売値);
😬
#javajo
?929
14/57
double/floatまとめ
double/floatで金勘定してはいけません
例外:
集計・サマリ
証券のリスク計算
そのほか正確な金額がいらない場合
正確な金勘定をするためには
BigDecimalクラスを使いましょう
#javajo
15/57
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
BigDecimalの構成
BigDecimal: (整数値, スケール)
整数値任意桁数の10進の桁を表す
BigInteger値
スケール小数点を右端からいくつ左に
動かすかを表す int値
1 2 3 4 5スケール:3
整数値:
12345
#javajo
19/57
?12345000
BigDecimalの例
整数値 スケール 表される値
12345 0 12345
12345 3 12.345
12345 6 0.012345
12345 -3
#javajo
スケール≧0の時、
スケールは小数点以下の桁数です
20/57
BigDecimalの例
同じ数を表す(整数値, スケール)の
組み合わせは複数存在します
整数値 スケール 表される値
12345 3 12.345
1234500 5 12.34500
#javajo
同じ数
21/57
#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
#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
BigDecimalの丸め
金額の端数処理の例:
金額の小数部を四捨五入
小数点以下2桁を残して切り捨て
端数処理に使うメソッド:
setScale(新しいスケール, 丸めモード)
#javajo
BigDecimal 丸められた数
= 元の数 .setScale(2, RoundingMode.FLOOR);
例: オブジェクトは
変更されません
26/57
#javajoBigDecimalの丸め
setScale()による丸め:
3 2 FLOOR 3.00
1.7320 2 FLOOR 1.73
1.7320 2 CEILING 1.74
元の数 スケール 丸め 結果
27/57
BigDecimalの丸め
丸めモード:
CEILING / FLOOR 切り上げ / 切り捨て
UP / DOWN 0から遠い方 / 近い方
HALF_UP 四捨五入
HALF_DOWN 五捨五超入
HALF_EVEN 最近接偶数への丸め
UNNECESSARY丸めが必要になってしまったら
ArithmeticException
#javajo
28/57
× round(MathContext)
round(MathContext)メソッドは
金勘定の端数処理には使えません
#javajo
setScaleスケール(小数点以下の桁数)を指定して丸める
round最大精度(整数値全体の桁数)を指定して丸める
roundは、演算を繰り返すときに精度が膨れ上がって
メモリを食い潰さないようにするためのメソッドです
29/57
× 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
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
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
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
#javajoBigDecimalの落とし穴2
スケールが違うとequalsは成立しません
33.3 + 66.7
new BigDecimal("100")
100.0(スケール1)
100(スケール0)
! equals
36/57
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
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
BigDecimalまとめ
BigDecimalは整数値とスケールで
構成されます
new BigDecimal(String)で定数を
作りましょう
端数はsetScaleで丸めましょう
BigDecimal.equalsはスケールも
比較対象です
#javajo
40/57
#javajo割り算
割り算はフクザツ
ゼロ除算
割り切れないことがある
結果の桁数が大きくなるかも
例: 3/64 = 0.046875
可能なら逆数を掛ける方が良いです
例: numを4で割る
num.mupltiply(new BigDecimal("0.25"))
42/57
#javajoBigDecimalの割り算
金勘定のための割り算メソッド:
divide(除数, スケール, 丸めモード)
指定したスケールで結果を戻す
割り切れない時は丸める
結果が指定したスケールに収まらない
ときは丸める
divideと名のつく他のメソッドは罠です
43/57
例: 費用配賦 #javajo
年間の費用を12ヶ月ごとに均等割します
ただし、ひと月の費用は小数点下2桁までに
切り捨てて、端数は最後の月に寄せます
年間の費用を12ヶ月ごとに均等割します
ただし、ひと月の費用は小数点下2桁までに
切り捨てて、端数は最後の月に寄せます
注意! 費用がマイナスの時は?
0の方向に丸める: -0.123 → -0.12
-∞の方向に丸める: -0.123 → -0.13
ここでは-∞の方向に丸めることにします
44/57
例: 費用配賦 #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
Money and Currency API
JSR 354 Money and Currency APIは、
金勘定に関するAPIです
通貨、金額、丸め、通貨換算、書式化…
Java SE 9で採用予定です
クレディ・スイスが主導しています
#javajo
47/57
なぜクレディ・スイス?
スイスの特殊条件が影響?
周りは全部ユーロ圏
決済用代替通貨
WIRフラン , WIRユーロが流通している
現金決済の最小単位は
5ラッペン(5/100スイスフラン)
現金以外の最小単位は1ラッペン
たしかに標準APIが欲しくなりそうです
#javajo
48/57
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
構成
API: javax.money 実装
通貨インタフェース
金額インタフェース
丸めインタフェース
SPI
SPI
SPI
通貨実装のプロバイダ
金額実装のプロバイダ
丸め実装のプロバイダ
実装はAPIから独立しています(参照実装: Moneta)
複数実装を組み合わせて使うことも可能です
#javajo
50/57
通貨: CurrencyUnit
CurrencyUnit 日本円
= Monetary
.getCurrency("JPY");
通貨コード
に対応する通貨
CurrencyUnit 地域の通貨
= Monetary
.getCurrency(Locale.getDefault());
ロケールに
対応する通貨
#javajo
51/57
金額: MonetaryAmount
Monetaが提供する金額実装:
Money: 任意スケールの金額
FastMoney: 固定スケール(5)の金額
RoundedMoney: 演算ごとに丸める金額
Money 定価 = Money.of(1000, 日本円);
System.out.println(定価);
#javajo
JPY 1000
52/57
丸め: 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
通貨換算: 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
Money and Currency APIまとめ
結構な重量級APIです
つかいでのありそうなシステム
扱う通貨が可変
複数通貨間の換算を行う
関連情報へのリンク集:https://java.net/projects/jjug/pages/Adopt-a-JSR-JavaSE9
#javajo
55/57