java の int と Integer と BigInteger の違いや float や double を理解する

先日プログラミングコンテストの過去問を haskell でやった時に、べき乗をなんやかんやするロジックを書いた時に最終結果が`12340000`で欲しかったのに`12340000.0`になってしまって通りませんでした。恥ずかしい。

恥ずかしいけど、聞くは一時の恥聞かぬは一生の恥の解説とも言うし、知らないことは素直に学んで修めればなかったことにできるんです。できんの？

よくわからんけど、要するに曖昧なまま使ってた`int`みたいなものをきっちりまとめるぜって、そんな話。

## かるーく背景
普段は java / DDD で契約管理のシステムを作ってるんだけど、扱う数字なんてたかだか数万の整数くらいなんだよね。ちょっとした円だとか契約の数を数えたりだとか、その程度。
なんと月の請求に日割りがないしね。驚きだね。

DDD の value object とかのおかげで、生の`int`とかを触ることもあんまりないしね。

なのでこの記事を書く直前の例えば java の理解度はだいたいこんな感じ。

「`byte`？　よくわからんけど怖い」
「`long`？　長そ〜」
「`BigInteger`？　でかそ〜」
「`float`？　ふわふわ〜」
「`double`？　何が倍なの〜？とうおるるるるるるるる」

ってくらいの理解度。これ大マジ。

（double のイタリア語が doppio ってことだけは知ってたんだよォォォ）

なのでそれくらいの人がまとめたんだよってことだけ了承してね！

## haskell 版もよろしく
この記事の確認言語には java を使っています。

もともと haskell で確認してたのですが、いくつかの言語を確認した方がより理解が進むかと思って java でも試してみました。

python でも少し確認したりしたのですが、記事は本懐の haskell と仕事で使ってる java にしようかと思います。

というわけで、こっちもよろしく。→ [haskell の Int と Integer の違いや Float や Double や Rational を理解する](https://qiita.com/suzuki-hoge/items/17cf0cd3680ef10b2cc1)

## まずは概要
プログラミングに入る前に、数学の話です。

数学といっても代数とか圏論とかの怖いやつはでてこないです。僕も怖いので。
だいたいが中学校くらいまでの話。

まずはこれを見てください。

![スクリーンショット 2020-03-15 23.42.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/fa490d8f-9ffd-dda9-998b-f1bf1425df4b.png)

ざっくり説明します。

### 実数と虚数
普段目にする数はだいたい`実数`です。

対して`虚数`は便利なので発明された数ですが、現実には存在しません。
代表的なのが`√-1`もしくはそれを`i`と表現したものですね。わかりやすいですね。僕はよくわかりません。

`虚数`は**二乗して 0 未満の実数になる数**で、`実数`は**それ以外**と定義されます。

ここを細かく考える気はないので、「だいたい`実数`」くらいで大丈夫です。

### 有理数と無理数
`実数`の分類は真剣に考えます。

`実数`は`有理数`と`無理数`に大別されますが、`有理数`は**整数の比で表現できる数です**。

対してそれ以外の**整数の比で表現できない数**を`無理数`と言います。

### 整数と有限小数と循環小数
いずれも`有理数`です。

`整数`は説明するまでもありませんね。
`3`は`3/1`と表現できるので`有理数`です。

有限小数は`0.5`の様な終わりのある`小数`です。
`1/2`の様に`整数`の比で表現できます。

対して`0.333...`や`0.142857142857142857...`の様に同じ数字の繰り返しが無限に続く`小数`を`循環小数`と言います。
これも`1/3`や`1/7`の様に`整数`の比で表現できます。

### 負の整数と正の整数とゼロと自然数
`整数`は一番馴染みがあるのであまり問題ないと思いますが、一応。

`負の整数`は`-1`や`-5`のことで、`-5/1`の形で表現できます。

`0`は`0/1`ですね。

`正の整数`についても同様です。

また、`正の整数`を`自然数`とも言います。（`0`を含めるかは本記事では問いません）

### 小数と分数
`少数`と`分数`についての補足です。

`分数`は**数の比**で表現される数であり、一見`有理数`と同じな気がします。
が、`有理数`は**整数の比**なので、分数の方が広い概念です。

例えば`1/√2`なんてのもありです。これは**整数の比**ではないので`無理数`です。
（だいたい 0.7 なので二乗するとだいたい`0.5`で`0`より大きいので`虚数`ではないですね。）

また、例えば`無限小数`というものがありますが、先ほどの図で言うと、`有理数`の`循環小数`も`無理数`も`無限小数`です。
循環しているかしていないかの違いですね。

## 押さえておきたい英語
以上の要点を押さえつつ、我々プログラマは英単語も知らないと困るので、ざっくり整理しておきます。
（と言っても絵には英語も入ってますが。）

`実数`は`real number`で`虚数`は`imaginary number`です、イメージつきやすいですね。

あまり馴染みはないですが、`有理数`は`rational number`です。コピペで書いてると稀にぶち当たります。

`ratio`が`比率`という意味なので変数名で使ったことがある人もいるのではないでしょうか。

また、絵にはないですが`小数`は`decimal`で`分数`は`fraction`です。

## プログラミングの世界へ ( java )
さっそく java でサンプルコードを見たいところですが、人間の世界とコンピュータの世界では大きく違うことがあります。

それは「メモリが有限」ということです。

どこにそれが関係するかと言うと、例えば「すげーでけー数」と「無限小数」です。

### 固定長整数
例えば java の`int`は 32bit 固定の`整数`です。

コンピュータのメモリには限界があるので、数値を 32 の`0|1`の範囲に限定して表現します。

### 多倍長整数
対して`多倍長整数`は**扱う数に応じて動的にメモリを確保する**数値の表現方法です。

理論上は無限の数を扱うことができます。（もちろんコンピュータのメモリの許す限りですが。）

### 固定長整数と多倍長整数
みんな大好きオーバーフローはこの`固定長整数`が引き起こします。

たとえば java の`byte`は 8bit 固定の`整数`です。
頭の 1bit を正負の符号に、残りを値の表現に使います。

`0000|0000`から`1`ずつ増加を始め、`0111|1111`から`1000|0000`になるところでオーバーフローし、
`1111|1111`から`1|0000|0000`になるところで 9bit 目が範囲外になり`0000|0000`として扱われます。
（見やすくするために 4 桁ごとに`|`を入れています。）

![スクリーンショット 2020-03-16 0.36.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/1a2c8e7c-db86-881b-a21b-bfdb3f5151e4.png)

対して`BigInteger`は`多倍長整数`です。
こいつは桁あふれが起きそうになると、動的にメモリを確保するのでオーバーフローしません。

![スクリーンショット 2020-03-16 2.43.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/cd3107be-16a0-23d4-c1d4-2384b5dccd23.png)

（符号や値の保持については実装方法によるので、上図はイメージです。）

`固定長整数`はメモリ効率や性能に優れ、`多倍長整数`は精度に優れます。
これらは適材適所です。

### 浮動小数点
`整数`と同じく`小数`においても同様の考え方があります。

`浮動小数点`とは数値の**表現方法の一つ**で、`固定長`の`仮数部`と`指数部`を持つ表現方法です。

ざっくり`仮数部`は値で`指数部`は桁を表していると考えれば大丈夫。

例えば二進数の`0.00000101`は`101 * 2^-8`の様に表されます。

ただこれだと`10.1 * 2^-7`とかでも表現できちゃうので、`IEEE754`と言う規格で`仮数部`は`1.x`にすると決まってます。なので`1.01 * 2^-6`です。
`1.01e-6`なんて書いたりもします。

コード書いていてたまに出る`e`入ってるやつはこれだね。怖かったけど克服したぞ。

`仮数部`と`指数部`によって小数点を打つ位置が変わってくるので`浮動小数点`と言うのかな。
一方で対になる単語は`固定小数点`で、例えば`整数`がこれに含まれます。

## java で確認
前置きが長くなりました。ここからはガシガシ java で確認していきます。

type            | 説明                    
:--             | :--                     
byte, Byte     | 8bit 固定長整数         
short, Short   | 16bit 固定長整数        
int, Integer   | 32bit 固定長整数        
long, Long     | 64bit 固定長整数        
float, Float   | 単精度浮動小数点 ( 32bit )
double, Double | 倍精度浮動小数点 ( 64bit )
BigInteger      | 多倍長整数              
BigDeciaml      | 多倍長小数              

以下のコードは`System.out.println`に相当するものは省略し、その行のコメントがその結果とします。

### byte, short, int, long
たくさんあるけど恐れることはありません。

こいつらは全部`固定長整数`で、違いは表現できる精度しかありません。

```java
Byte.MAX_VALUE;       // 127
Short.MAX_VALUE;      // 32767
Integer.MAX_VALUE;    // 2147483647
Long.MAX_VALUE;       // 9223372036854775807
```

例えば`Integer`の上限値に`+1`すると、オーバーフローします。

```java
Integer.MAX_VALUE + 1;    // -2147483648
```

#### 相互変換
また当然ですが、精度の低い方から大きい方へのキャストは問題ありませんが、逆は正しく行えません。

```java
short s = 20000;

(int) s;    // 20000
```

```java
int i = 40000;

(short) i;    // -25536
```

### ところで int と Integer の違い
もともとの趣旨とは離れるのですが、案外面白いのでせっかくと言うことで。

java の`int`はプリミティブ型で、`Integer`はクラス型と言います。

主な違いはすんごいざっくり言うと「`int`は`null`が許容されない」のと、「`int`は`List<T>`とかの`T`になれない」くらいです。
精度とかについては`int`と`Integer`に違いはありません。これ大事。

また java にはコンパイラがよしなに相互変換してくれる仕組みがあるので、大体の場合はあんまりどちらかを気にしなくても大丈夫です。

#### 相互変換、の前にメモリの話
普段あんまり考えることはないかもしれませんが、スタック領域とヒープ領域について超ざっくり説明します。

例えばこの様なコードを書いた場合。
（`int`と`Integer`の変数を区別しやすくするため、**本記事では**変数名の先頭に大文字を使います。）

```java
Integer Ia = new Integer(1);
```

この場合、メモリはこんな感じになってます。

![スクリーンショット 2020-03-16 1.31.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/eeef6017-a5c5-4388-e7b4-38b4702a6a59.png)

`new`をするとスタック領域の`Ia`という変数に何かが入ります。
なんとなく`Ia`にはインスタンス自体が入ってる気がしますが、入ってるのは**矢印だけ**です。恐ろしい言い方をすると**ポインタ**です。

作られたインスタンスはヒープ領域に入っています。

対してプリミティブ型の`int`はスタック領域にそのまま確保されます。

```java
Integer Ia = new Integer(1);
Integer Ib = new Integer(1);
int ia = 1;
int ib = 1;
```

なのでこんなコードを書いた場合の絵は下のようになります。

![スクリーンショット 2020-03-16 1.36.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/967ddcba-aa26-7ffe-370d-8139009d3f9e.png)

#### 同一性と同値性
「java で比較に`==`を使うんじゃあねぇ」と怖い人に怒られたことがある人はいっぱいいると思いますが、せっかくなのでなんでなのか見てみましょう。

クラス型における`同一性`は**同じインスタンス**かを、`同値性`は**同じ値**かを比較することです。
前者は`==`で、後者は`equals`によって行われます。また同値性は実装に依存します。
（例えば DDD の entity の比較では identity の一致のみで同値とみなす場合もあります。）

プリミティブ型の`==`はシンプルに値を比較します。

![スクリーンショット 2020-03-16 1.40.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/c5b8f05e-1c74-a31d-5eb5-1809366b44c3.png)

なので`Ia == Ib`は**宛先の違う矢印なので false** です。`Ia.equals(Ib)`は**宛先の値が同じなので true** です。

例えるなら「A さんも B さんも 500 円玉を持っていて、**物理的には違う硬貨**だけど**価値は同じ**」と言った感じです。

#### auto boxing と auto unboxing
スタック領域とヒープ領域、比較について理解したところで、相互変換についてです。

`int` -> `Integer`を **boxing** , 逆を **unboxing** と言います。
ラッパークラスの箱に入れるイメージかな。

以下のコードが実行できるのは **auto boxing | auto unboxing** によるものです。

```java
Integer Ia = new Integer(1);
int ia = Ia;                   // unboxing
```

![スクリーンショット 2020-03-16 1.47.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/13d7cb0a-2ca0-3a1c-b040-6dfa779a327b.png)

```java
int ib = 1;
Integer Ib = ib;               // boxing
```

![スクリーンショット 2020-03-16 1.47.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/d16bedc3-15dc-0353-08e0-aada3b0f5bf9.png)

内部的にはスタック領域に値を持ってきたり、ヒープ領域にインスタンスを作って参照を得たりしています。
（実際には元の値は消えませんが、イメージしやすいので薄くしています。）

#### 余談　落とし穴
さて、以下のコードは`true`と`false`どちらになるでしょうか。

```java
int ia = 1;
int ib = 1;
Integer Ia = ia;
Integer Ib = ib;

Ia == Ib;    // true or false ?
```

**auto boxing** によって`new`されるので`Ia`と`Ib`の矢印は違うはずです。上の絵でもそうなってます。

が、これ`true`になります。

どうやら **auto boxing** は`Integer#valueOf`で、**auto unboxing** は`Integer#intValue`によって実現される様です。

```Java
Integer Ia = Integer.valueOf(ia);
Integer Ib = Integer.valueOf(ib);
```

で、肝心の`Integer#valueOf`ですが、こんな実装になっています。

```java
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
```

どうやらよく使う`-128`~`127`はキャッシュされているみたいですね。なので上のコード例だと`new`されないです。

こんなコードだとちゃんと`false`になります、理解は間違ってなかった様で安心だ。

```java
int ia = 1000;
int ib = 1000;
Integer Ia = ia;
Integer Ib = ib;

Ia == Ib;    // false
```

あ、ちなみに内部では **auto unboxing** に`Integer#intValue`を使うと言うことは、`Ia`が`null`の場合に **auto unboxing** をすると`NullPointerException`がでますよ。

#### int と Integer の違いまとめ
+ 精度は同じ
+ 比較はちょっと気をつけろよ
+ 相互変換は便利だけど完全に置き換えてくれるわけではないから気をつけろよ

ってことですね。

本記事においては以後`int`と`Integer`や`float`と`Float`は精度の違いがないため、特に断りなくサンプルコードで都合が良い方を用います。

### float, double
`整数`は押さえましたね。次は`小数`です。

`double`の何が`倍`なんだって思ってましたが、勉強すれば明瞭ですね。
`float`は 32bit を使って、`double`は 64bit を使って値を表現するということでした。だから倍精度。

コンピュータのメモリが有限である以上`無限小数`を完全に表現することは不可能なので、誤差が出る前提で扱わなければなりません。

例えば十進数の`0.01`は二進数だと有限で表現することができません。
有限で表現できない以上どこかで諦めなければいけず、それを繰り返せば誤差が大きくなるのはなんとなく感覚で理解できますね。

で、どんな誤差が出るか、です。試してみましょう。

```java
float f = 0;
for (int i = 0; i < 100; i++) {
    f += 0.01f;
}

double d = 0;
for (int i = 0; i < 100; i++) {
    d += 0.01d;
}

f;    // 0.99999934
d;    // 1.0000000000000007
```

`double`の方が`1.0`に近いですね。

#### 相互変換
`float`と`double`の変換も、`short`と`int`と同様に精度の高い方から低い方へ変換すると壊れます。

```java
f;             // 0.99999934
d;             // 1.0000000000000007

(double) f;    // 0.9999993443489075
(float) d;     // 1.0
```

`double`から`float`にした場合は欠けてしまっていますね。

またそもそも有限なので、単純に以下の様な値で誤差が出ます。

```java
10d / 3d;       // 3.3333333333333335
1.00000001f;    // 1.0
```

### BigInteger, BigDecimal
お待たせしました、`多倍長`の奴らです。

こいつらは桁に応じて動的にメモリを確保するので、オーバーフローしないし誤差も出ません。なんかすごい。

さっそく試してみましょう。

#### BigDecimal
`小数`の`BigDecimal`から試してみます。初っ端から気前よく巨大な`整数`を扱ってみましょう。

```java
BigDecimal bd = new BigDecimal(Long.MAX_VALUE);

bd;                           // 9223372036854775807

bd.add(new BigDecimal(1));    // 9223372036854775808
```

`long`の上限に加算してもオーバーフローしてません。
もっと思い切りよく足しても全然大丈夫。

```java
bd.add(bd);                   // 18446744073709551614
```

`小数`も加算できる。

```java
bd.add(new BigDecimal(0.5));  // 9223372036854775807.5
```

本命？の`小数`の誤差はどうでしょうか。

```java
BigDecimal bd = BigDecimal.ZERO;
BigDecimal x = new BigDecimal(0.01);
for (int i = 0; i < 100; i++) {
    bd = bd.add(x);
}

bd;    // 1.00000000000000002081668171172168513294309377670288085937500
```

`double`の`1.0000000000000007`より精度が良いですね。（`toString`出来ているのはすげぇ頑張ってるからです。）

`double`で誤差が出た`10d / 3d`はどうでしょうか。

```java
BigDecimal bd10 = new BigDecimal(10);
BigDecimal bd3  = new BigDecimal(3);

bd10.divide(bd3);    // ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
```

`terminating`って単語は冒頭のベン図っぽいもので見ましたね、有限小数じゃあないって怒られてます。

誤差のでる値は誤差のあるまま持たせてくれないみたいですね。
切り捨てたり切り上げたりを明示しないとだめみたいです。

```java
bd10.divide(bd3, RoundingMode.FLOOR) // 3

bd10.divide(bd3, RoundingMode.CEILING) // 4
```

### BigInteger
こいつは簡単です。`小数`の扱えない`BigDecimal`です。

```java
BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE);

bi;            // 9223372036854775807

bi.add(bi);    // 18446744073709551614
```

`BigInteger`には`0.5`みたいな`小数`を渡せる生成メソッドがないので、`BigDecimal`と比べると「これだけ」です。

もう大丈夫。怖くない。

#### 余談　破壊/非破壊
ところでなんとなく java の感覚だと`add`すると破壊する気がしませんか？
`List#add`とかそうじゃん。

けど、`add`するたびにメモリを確保し直す可能性があることを理解していると、非破壊で毎度違うインスタンスを作ってるって考えやすいよね。
（実装方法によるので immutable の場合もあるけど mutable の場合もあるらしい。）

## まとめ
ながーい記事になったけど、やってみて感じた java における数値表現の要点は３つだけだ！

+ `byte`, `short`, `int`, `long`の違いは精度だけ、それぞれ限界があるぜ
+ `float`, `double`も違いは精度だけ、`小数`は有限のメモリでは表現できないので誤差が前提なんだぜ
+ `BigInteger`と`BigDecimal`は（メモリがある限り）限界がない`整数`と`小数`だぜ

これだけだ！`int`と`Integer`の違いは数値表現ってより java のお勉強としてがんばるんだ！

いやーそれにしても勉強になった。普段どれだけ適当にやってきたかを痛感した。

そしてこれを理解したらどうするかと言うと、やっぱドメインロジックとは切り離したいので value object を作って隠蔽するわけだ！
きっちり理解したので普段の業務（ドメイン実装）ではやっぱり使わないわけだ！なんというパラドクス！