【Java】Javaでの数字の使い方をちゃんとしよう!

前に書いた記事のなかでそれなりに閲覧数がある話題なので需要があるのかな?と。

Javaの数値の話です。

この辺多分初めてプログラム言語を触りました!って人は必ず注意され、
必ず先輩に言われる内容なので、知っておいて損はないと思います。

あとプログラミングの話題ですが、
プログラミングを知らなくてもわかる内容だとは思うので読み物として読んでもらえるとうれしいです。

■パソコン君は実は計算いい加減?

プログラミング言語に限った話ではないんですが、
コンピュータって数字に強いイメージがあると思います。

いや、まぁ実際強いし、めっちゃ計算とか早いんですが、
必ず正しい計算ができるかっていうと(基本)そんなことはありません。

背後にいるのが2進数だからですね。

プログラム君は整数の計算はめっちゃ得意です。
すばやく正確に計算してくれます。

でも小数は結構苦手です。小学生みたいですね。

というのも桁落ちと情報落ちという話題が必ず付きまとうからです。
折角なので紹介します。

プログラミングに限った話ではないですが、有効数字というものがあります。
何桁まで表示しますか?というやつですね。

1/3+1/3+1/3=1ですよね。

でも、1/3を小数であわらすと0.333333333…..
無限に続く循環小数になってしまうので、小数の有効桁数を5桁とかにすると
1/3 = 0.33333になります。
となると
0.33333 + 0.33333 + 0.33333 となり、0.99999になってしまいます。

答えが合いませんよね。
コレが桁落ちです。

情報落ちというものは限られた有効桁数のなかで、
めっちゃ大きいものとめっちゃ小さいものを計算した場合に発生します。

有効数字7桁で次の計算をしようとします。

111111.1+1.111111

答えは有効数字7桁なので111112.2
0.011111は無かったことになります。

もっとひどい例として、パソコン君は0.1を表現できません。
パソコン君は2進数で情報を保持するわけです。

0.1を2進数であらわすと
0.0001100110011001100110011001100110011001100110011001101…..無限
でもコレを10進数であらわすと

0.1000000000000000055511151231257827021181583404541015625
となり、そもそも0.1ではなくなってしまいます。

なので、こんなコードを書いてしまうととんでもない事に。。。

■でも実際計算できてね?

そうなんです。計算できてるんです。
上に書いたことは基本的に浮動小数の世界の話で
「小数で計算が狂うなら小数を使わなければいいじゃない」
みたいな精神(かどうかはわからないけど)で計算してこういった意図しない丸めとかを
発生しないようにする仕組みがあります。

それがJavaだとBigDecimalってクラスです。

こんな風にBigDecimalというクラスを使うと小数の計算も正しくできます。

■プログラミングでの数字の種類

プログラミングでは大抵の言語で浮動小数点を考慮した定義、
整数しか使えないぜの定義、数学的に特化した定義というものがあります。

Javaだとこんな感じですね。

short:16ビット整数 -32768~32767
int:32ビット整数 -2147483648~2147483647
long:64ビット整数 -9223372036854775808~9223372036854775807
float:32ビット単精度浮動小数点数
double:64ビット倍精度浮動小数点数

intで小数は扱えませんし、それぞれの型には入力レンジがあります。
ビットというのが大きいほど多くの数字を表現することが可能です。

でもfloatは32ビットですが、小数点情報を保持するので、同じ32ビットのintよりも
表現できる値の最大値は小さくなります(floatは有効数字7桁までです)。

何でもかんでもBigDecimalにすればいいというものではないし、
整数は一番大きい数字までいける使えるからという理由で何でもかんでもlongで定義すればいいというわけではないです。

だいぶ潤沢になっているので今ではそこまで「メモリ消費を考えて実装しよう」とかいわれないですが、
1~100くらいまでの自然数しか使わないのにlongで型宣言するのはもったいない精神が。。。

できれば入りうるものを考慮したうえで型とかを宣言できるといいですね。

intの最大値が21億くらいということは覚えておいて、
整数で基本はintで宣言して、21億超えそうならlongにするという使い方とかがいいと思います。

BigDecimalはまたちょっと違って、
BigDecimal = BigInteger * 10の何乗か
で表現します。

でもってBigIntegerはInteger(上のintと同じ)を組み合わせて表現します。
その組み合わせはメモリ食いつぶすくらいまでもてるので、
いちじゅうひゃくせんまん・・・って数えたときの無量大数が128桁で、
1000000桁くらいまでは特に何も気にすることなく平気で計算できるので、
BigDecimalの桁数については基本考慮に入れなくて平気です(理論値はもっと大きいです)。

考慮に入れないといけないようなことをしている人は
プログラミング言語と親友みたいな人だと思うのでいまさら釈迦に説法ですね。

■ならつねに計算は正しくできるのか?

これだけ色々な宣言方法があって、結構いけそうです。

大抵の解説書とかは
「基本intとかで金額計算とかはBigDecimal使えば何も問題ないよ!」
って話で終わっています(終わっていることが多いです)。

そんなことはないです。

BigDecimalは小数の計算ができるだけで、結局桁落ちは発生します。

金額計算で絶対あるであろう消費税。現在8%です。

外税計算だとして、0.08なんて掛け算したら大抵の数で小数点が発生します。
しかも日本円の場合最小単位が1円なので、
四捨五入とか、切り上げとか切捨てとかをする必要があります。

有名な話
10円のうまい棒を買ったときに、単純計算だと消費税は0.8円なので
切り上げ計算や四捨五入していた場合は消費税が1円になり11円になります。
切捨て計算の場合は消費税は0円になり10円です。

でもコレ、どちらの計算したとしても結局1本ずつ10回ばらばらに購入したときと
1回で10本一括で買ったときで計算結果はずれます。

切り上げや四捨五入の場合は110円、切捨ての場合は100円ですよね。

Javaとかプログラミングの世界ではないんですが、
金額計算はBigDecimalを使っとけばAll OKという話ではないですよーという例でした。

コメント

タイトルとURLをコピーしました