Q068891のメモ帳

気が向いたときにLinuxだったり、プログラムだったり、適当なメモをざっくばらんに書いてます。

Javaでの数値計算の誤差や限界値

int型の計算誤差

int型同士の[掛け算、足し算、引き算]で誤差が発生するのはint型の最大値を超えてしまった場合か、最小値を下回ってしまった場合のみです。尚、当たり前ですが少数点を含む数値はだめです。整数に限ります。

int型の最大値 = 2147483647
int型の最小値 = -2147483648

この値を超えないよう注意さえすればint型では掛け算、足し算は誤差なく実施できます。
ただし、int型は小数点を切り捨てるため、割り算では大抵誤差が発生します。

例:1/2 = 0.5
  0.5はint型では0として扱われるため
  1/2 = 0 となってしまう。

double型の誤差について

double型では値を格納する際に誤差が発生する可能性があります。
そのため、どのような計算でも誤差が発生する可能性があります。

尚、double型が扱える範囲は

最大値 1.79...E+308
最小値 4.9E-324

となっています。

※double型は指数形式で値を格納し、仮数部の桁数の限界は15桁までとなり、このような最大値、最小値になっています。

かなり大きな値のため気にすべきは最大値、最小値よりも下記★の問題です。

double型は2進数で値を格納します。★元々は10進数の値を2進数に変換して、値を格納するためそこで誤差が発生してしまいます。
例えば、10進数の0.1を、2進数に変換しようとすると永遠に同じ値 0.000110011・・・となってしまいます。

よって、値をどこかで丸める(切り捨て/切り上げ等)しかありません。(丸め誤差)

指数形式とは?

[10進数形式 ⇒ 指数形式]
  3210 ⇒ 3.21E+03 (+3.21 x 10^3という意味)

[+を符号] [3.21を仮数] [10を基数] [3を指数]と呼びます。
※例では便宜上10を基数としてますが、double型の本当の基数は2です。

double型の指数形式仕様

double型の指数形式は具体的には以下の様になっています。

符号部(1bit) + 指数部(11bit) + 仮数部(52bit)

10進数の値を仮数部に格納する際は、一の位が「1」で他が小数点以下になるように指数を調整しているため、仮数部の値は必ず「1.***」となり、一の位は常に「1」となっています。
常に同じ値のものを格納してもしょうがないため、実際は「***」の部分のみ仮数に格納していますので、仮数部は52桁ですが、実際は53桁が有効桁数となっています。

BigDecimal

BigDecimal型 掛け算、足し算について

BigDecimalはBigInteger型の変数を持っていてそこに数値を格納しているため、掛け算、足し算でエラーが発生するのはBigIntegerの最大値/最小値を超えてしまった場合のみとなります。

BigDecimal型 割り算について

割り算は1/3などの場合0.33333…と無限に続きこのままでは無限桁必要になってしまうので、どこかで桁を調節しなければいけません。

BigDecimalは任意の桁で丸め操作ができるようになっており、誤差を調整できます。(すばらしい!)

BigIntegerの限界

BigDecimalはBiginteger型の変数に値を格納しているため、細かいところは置いといて、BigDecimalが表現できる最大値==Bigintegerの最大値と考えて問題ないと思われです。

BigIntegerはint型の配列を持っていてそこに数値を格納していますが、byte型で表現したものをint型に変換して格納しています。

つまり、int型変数を複数用いて、大規模なbyte型を表現しています

配列の長さの限界はint型の最大値2147483647であるため、BigIntegerは 2147483647 * 32bit = 68719476704 bit 使用できることになります。
よって、10進数でBigDecimalの限界値を表すと2^68719476704となると思われです。

ただ、BigDecimalのソースを見たところ、どうも本来小数点の部分も同じBigIntegerの変数に格納してるっぽいです。
そのため、小数桁を整数にシフトしてから2^68719476704を越えていないか確かめる必要があると思われです。(例えば94.267だったら94267と見る必要がある)

しかし、int型配列のみでも 68719476704bit \approx 8.58GB となっているため、桁数制限の前に大抵メモリの問題が発生するため、現時点では最大値を気にする必要はほぼ無いと思います。
そのため、自分が今からやる計算はどこまで誤差が許容できるのかを把握することがBigDecimal型では重要になると思います。

他に注意すべき点

double型からBigDecimalに変換するときに

double d = 0.1;
Bigdecimal b = new BigDecimal(d);

とやってしまうと、double型に一度しているため正確に値が表現できていない可能性があります。

そのため、以下の様にStringに変換してからBigDecimalのオブジェクトを生成してやると吉です。

double d = 0.1;
BigDecimal b = new BigDecimal(Double.toString(d));

まとめ

色々書きましたが、Java数値計算をするときの注意をまとめるとこんな感じになります。

  • int型の足し算/引き算/掛け算は、最小値/最大値の範囲からはみ出ない様に注意。小数点になってしまうとint型は小数点以下を切り捨てるので、割り算とかはあんまり向いてない。
  • double型は2進数で値を格納するので、値を格納した時点で誤差が発生する可能性あり。誤差を許容できる計算のときに使うこと。
  • BigDecimalの足し算/引き算/掛け算は、最大値/最小値に気をつければok。ただし、仕様上の最大値/最小値の範囲がかなりでかいのであんまり気にしなくてOK。割り算とか小数点が出てくる計算は、どこで小数点をどのように丸めるか等、BigDecimalは誤差調整ができるので、実施したい計算がどこまで誤差が許容できる計算なのか把握して、BigDecimalを適切に扱う方が大事。ただし、計算速度はint型、double型よりも遅い。