haskell の Int と Integer の違いや Float や Double や Rational を理解する

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

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

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

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

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

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

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

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

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

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

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

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

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

というわけで、こっちもよろしく。→ [java の int と Integer と BigInteger の違いや float や double を理解する](https://qiita.com/suzuki-hoge/items/dcf86a48404ec3bb258c)

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

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

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

![スクリーンショット 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`です。

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

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

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

### 固定長整数
例えば haskell の`Int`は僕の環境では 64bit 固定の整数です。

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

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

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

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

haskell の`Int`を 8bit 固定の整数だと仮定して考えます。
頭の 1bit を正負の符号に、残りを値の表現に使います。

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

![スクリーンショット 2020-03-17 0.33.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/60e2b7ea-1452-b4af-a012-9f9b308b5b95.png)

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

![スクリーンショット 2020-03-17 0.33.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/45318fef-72af-5ef6-0d0a-c8e81cc48035.png)

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

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

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

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

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

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

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

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

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

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

type    | 説明                      
:--     | :--                       
Int     | 64bit 固定長整数          
Integer | 多倍長整数                
Float   | 単精度浮動小数点 ( 32bit )
Double  | 倍精度浮動小数点 ( 64bit )

以下のコードは repl の実行とその結果です。

また、この絵は「なんとなく数値っぽい型とそこに定義されていた関数を整理した絵」です。

![スクリーンショット 2020-03-22 0.27.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/2f2f819a-fb1a-a6ae-2b79-c7e40b5bcd63.png)

`Int`は`Integral`クラスのインスタンスになっているので`Int -> Integral`と線を入れています。

```haskell
ghci> :info Int
data Int = GHC.Types.I# GHC.Prim.Int# 	-- Defined in ‘GHC.Types’
instance Eq Int -- Defined in ‘GHC.Classes’
instance Ord Int -- Defined in ‘GHC.Classes’
instance Show Int -- Defined in ‘GHC.Show’
instance Read Int -- Defined in ‘GHC.Read’
instance Enum Int -- Defined in ‘GHC.Enum’
instance Num Int -- Defined in ‘GHC.Num’
instance Real Int -- Defined in ‘GHC.Real’
instance Integral Int -- Defined in ‘GHC.Real’
instance Bounded Int -- Defined in ‘GHC.Enum’
```

### Int
先述した通り`Int`は`固定長整数`です。

これを見る限り、java の`Long.MAX_VALUE`と同じなので 64bit ですね。
    
```haskell
ghci> maxBound :: Int
9223372036854775807
```

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

```haskell
ghci> ( maxBound :: Int ) + 1
-9223372036854775808
```

ところで`maxBound`は`Bounded`に定義されています。
他に`Bounded`のインスタンスになっているものは例えば`Bool`や`Ordering`があります。

```haskell
ghci> ( minBound, maxBound ) :: ( Bool, Bool )
(False,True)

ghci> ( minBound, maxBound ) :: ( Ordering, Ordering )
(LT,GT)
```

### Integer
対して`Integer`は`多倍長整数`です。気前よく巨大な`整数`を扱ってみましょう。

```haskell
ghci> n = 9223372036854775807 :: Integer

ghci> n + 1
9223372036854775808
```

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

```haskell
ghci> n + n
18446744073709551614
```

ところで`Integer`が`Bounded`のインスタンスでないのは、`多倍長整数`なので上限がないからですね。ここまで理解していれば至極納得。

### 余談　型注釈
ところでサンプルコードで`:: Int`や`:: Integer`の様に型注釈をつけていますが、これをつけないとどうなるでしょうか。

```haskell
ghci> n = 42

ghci> :t n
n :: Num p => p
```

まだ具体的には断定されていないみたいですね。

haskell は`数値リテラル`を必要になるまでは抽象的に扱っておいて、必要に応じて型を決めてくれます。
例えばこの`n`の定義後に`toInteger n`なんてあれば haskell は`Integral`だと思って扱おうとするわけです。

冒頭で触れた僕のわからなかったエラーの原因は完全にこれですね。
僕には`f 8 0 []`って書いた時の値は`Int`に見えていたのですが、コンパイラはそう見てなかったということなのでしょう。~~悲しい~~ 未熟ゆえのすれ違い。

ちょっと確認してみましょう、以下のコードはどちらも実行できます。

```haskell
main = do
    let n = 42
    print $ toInteger n
    -- print $ floor n
```

```haskell
main = do
    let n = 42
    -- print $ toInteger n
    print $ floor n
```

が、これは実行できません。

```haskell
main = do
    let n = 42
    print $ toInteger n
    print $ floor n
```

`n`は`Integral`なのか`RealFrac`なのかどっちだ、ってなってしまうからですね。

ですがこれは実行できます。

```haskell
main = do
    let n = 42
    print $ toRational n
    print $ floor n
```

これは`RealFrac`の定義がこうなっているからです。

```haskell
ghci> :info RealFrac
class (Real a, Fractional a) => RealFrac a where
```

絵を目で辿れば把握できそうですね。整理して良かった。

### Int と Integer の変換
絵を見ると、`Num`にある`fromInteger :: Integer -> a`を使って`Num`にしてから`:: Int`で断定すればたどり着きそうです。

```haskell
ghci> n = 5 :: Integer

ghci> :t fromInteger n
fromInteger n :: Num a => a

ghci> fromInteger n :: Int
5
```

で、想像の通り精度の低い方から高い方への変換はできますが、逆は正しくは行えません。

```haskell
ghci> n = ( 9223372036854775807 :: Integer ) + 1

ghci> fromInteger n :: Int
-9223372036854775808
```

逆に`Int`から`Integer`は絵にそのものずばりがあります。

```haskell
ghci> n = 5 :: Int

ghci> toInteger n
5
```

### 余談　加算
絵を見ると、`+`は`Num`に定義されています。

実は普段の`+`は`Int`の足し算をしている気がするかもしれないけど、先ほどの余談で確認した通り明示しない限り`Int`とは断定されていません。

```haskell
ghci> x = 1

ghci> y = 2

ghci> n = x + y

ghci> :t n
n :: Num a => a
```

ここから`x`と`y`が`Int`, `Integer`, `Float`, `Double`どれに断定されても大丈夫です。


```haskell
ghci> x = 1 :: Int

ghci> y = 2 :: Int

ghci> n = x + y

ghci> :t n
n :: Int
```

```haskell
ghci> x = 1 :: Float

ghci> y = 2 :: Float

ghci> n = x + y

ghci> :t n
n :: Float
```

ただし`(+) :: a -> a -> a`なので、`x`と`y`と`n`の型は全て同じである必要はあります。
ずれている場合は型を揃えてから計算します。

```haskell
ghci> x = 1 :: Integer
ghci> y = 2 :: Int
```

```haskell:ex1
ghci> n = x + ( toInteger y )

ghci> :t n
n :: Integer
```

```haskell:ex2
ghci> n = ( fromInteger x ) + y

ghci> :t n
n :: Int
```

ex1 と ex2 の違いももう大丈夫です。ちゃんとわかります。

ex2 はせっかく`fromInteger`で`x`を`Num`まで抽象化したけど、`y`が`Int`なので`x`も`Int`に断定されている。なのでオーバーフローする。はず。

```haskell
ghci> x = 9223372036854775807 :: Integer
ghci> y = 1 :: Int
```

```haskell:ex1
ghci> x + ( toInteger y )
9223372036854775808
```

```haskell:ex2
ghci> ( fromInteger x ) + y
-9223372036854775808
```

うん。ちゃんと理解できてる。

### 余談　除算
絵を見ると`(+)`は`Num`に定義されていますが`(/)`は違いますね。

なので普段行なっている`4 / 2`の演算は実は`Int`でも`Integer`でも行われてないのです。

```haskell
ghci> a = 4

ghci> b = 2

ghci> a / b
2.0
```

```haskell
ghci> a = 4 :: Integer

ghci> b = 2 :: Integer

ghci> a / b
No instance for (Fractional Integer) arising from a use of '/'
```

`Num`に抽象化してから計算しましょう。

```haskell
ghci> ( fromInteger a ) / ( fromInteger b )
2.0
```

### Int と Integer の違いまとめ
+ `Int`は上限があるぞ
+ `Integer`は上限が（メモリの許す限り）ないぞ
+ `Int`から`Integer`への変換は値が壊れる可能性があるぞ
+ 型を明記しない限り、haskell は必要に応じて型を決めるぞ

ってことですね。

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

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

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

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

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

```haskell
ghci> f = 0.01 :: Float

ghci> sum $ replicate 100 f
0.99999934
```

```haskell
ghci> d = 0.01 :: Double

ghci> sum $ replicate 100 d
1.0000000000000007
```

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

#### 相互変換
`Float`と`Double`の変換も、`Int`と`Integer`と同様に精度の高い方から低い方へ変換すると壊れます。

どうやって変換すれば良いのかな...
絵を見る限りでは`Rational`を経由したら良いのかな...

```haskell
ghci> f = 0.99999934 :: Float

ghci> d = 1.0000000000000007 :: Double

ghci> fromRational ( toRational f ) :: Double
0.9999993443489075

ghci> fromRational ( toRational d ) :: Float
1.0
```

絵には出せてなかったけど、調べたらこっちの方が良さそうかな。

```haskell
ghci> realToFrac f :: Double
0.9999993443489075

ghci> realToFrac d :: Float
1.0
```

どちらにせよ`Double`から`Float`にした場合は欠けてしまっていますね。

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

```haskell
ghci> ( 10 :: Double ) / ( 3 :: Double )
3.3333333333333335

ghci> 1.00000001 :: Float
1.0
```

では誤差の出る小数の計算はどうすれば良いのでしょうか。

### Rational
冒頭のベン図の様な絵で確認すると、`有理数`のことです。
さらっと冒頭で説明したこれのことですね。

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

haskell にはそのものずばりな`Rational`という型があって、そのまんま分数の様な形で扱っているみたいです。

```haskell
ghci> 1 :: Rational
1 % 1

ghci> 0.01 :: Rational
1 % 100
```

`Rational`は`Ratio Integer`のエイリアスになってるみたいです。

```haskell
ghci> :info Rational
type Rational = GHC.Real.Ratio Integer 	-- Defined in ‘GHC.Real’
```

`Ratio`は単純に**比**という意味なので、`Ratio Integer`は ↓ を表現しているのかな、今ならわかる気がしますね。

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


`Rational`も普通に`(+)`が可能です。

```haskell
ghci> r = 0.01 :: Rational

ghci> r
1 % 100

ghci> r + r
1 % 50
```

面白いですね、そのまんま分数の足し算みたいです。

これは 100 回加算しても誤差が出ない気がしますね。

```haskell
ghci> r = 0.01 :: Rational

ghci> sum $ replicate 100 r
1 % 1
```

すごい。

`小数`にしたければ、こんな感じでしょうか。

```haskell
ghci> fromRational ( sum $ replicate 100 r ) :: Float
1.0
```

もちろん、`Rational`から`小数`にする時に誤差が出ることはありえます。

```haskell
ghci> fromRational $ ( 10 :: Rational ) / ( 3 :: Rational )
3.3333333333333335
```

ここは気をつけなければいけませんね。

### Real, RealFrac, Fractional, Floating, RealFloat
こいつらは一体何者なのでしょうか。

#### Real
`Real`はまぁシンプルに**実数**でしょう。

#### RealFrac
> `小数`は`decimal`で`分数`は`fraction`です。

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

これを思い出すと、`RealFrac`は`実数の分数`なので**`有理数`も`無理数`も含むけど`整数`は含まない**と言ったところでしょうか...
`整数`を含むなら`実数`と変わらない気もするし、絵を見る限り`整数`は関係してません。

**切り捨て**や**切り上げ**もここで定義されています。

#### Fractional
今の僕にとってはここは興味の対象外なので、かなり推測が入ってしまいます。お許しください。

`Real`とは関係してない`分数`なので、おそらくは`虚数`の`分数`を考慮しているのではないかと思います。

`2/√-1`とかですかね...

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

これがそのもの floating point number だと思うのですが、`Real`を冠してない`Floating`が何かは正直まだ理解できてません。

`pi`とかは`RealFloat`だと思うんだけどな...

ちなみにこの記事まとめるまでは「`Float`が`RealFloat`なのはわかるけど`Double`も`RealFloat`なのかよ。イミワカンネ。」
とか思ってたんですよね...

`Float`と`Double`は仕組みは同じで精度の違いだけだからね...学んだよ...

#### 余談 べき乗
実は冒頭で触れた僕のエラーの直接原因は`(**)`でした。

`2 ** 3`が`8`と出力されて欲しかったのに`8.0`と出力されてしまうので「よくわからんけど適当にそこかしこに`:: Int`って書いてみよ」とかやって変なことになったのです。

なぜ`8.0`になるのかは、もう学んだので大丈夫です。

`Int`で扱うと巨大な数でオーバーフローしますし、`Integer`にしたら`小数`が扱えないからですね。

`浮動小数点`にして先述の`IEEE754`の規格通り`1.0e20`みたいに大きい数を扱えば、オーバーフローしないし`小数`も大丈夫です。

```haskell
ghci> 3000000000 ** 2
9.0e18

ghci> 4000000000 ** 2
1.6e19

ghci> 1.5 ** 2
2.25
```

仮に`(**)`の結果が`Int`だとすると、こんな感じになってしまうでしょう。

```haskell
ghci> (***) a b = floor $ a ** b :: Int

ghci> 2 *** 3
8

ghci> 3000000000 *** 2
9000000000000000000

ghci> 4000000000 *** 2
-2446744073709551616
```

~~よく考えられてるんですねぇ。~~ 不勉強でした、本当に恥ずかしい。

### 変換まとめ
変換関数がたくさん出てきたので、絵を少し違う切り口で整理してみました。

青は安全な関数、赤は値が壊れる可能性がある関数です。
（`int2Float`みたいなやつは`GHC.Float`に定義してありました、要 import です。）

また、`整数リテラル`は最初に`Integer`として扱われて`fromInteger`によって`Num`に、
`小数リテラル`は最初に`Rational`として扱われて`fromRational`によって`Fractional`に変換されるらしいです。

それぞれを緑で書き込んでみました。

![スクリーンショット 2020-03-22 0.14.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/da9afd78-7f5d-5dca-8f44-3c21794a431c.png)

`整数`になる点線を超える左向きの関数と、ベン図の外側に行く様な下向きの関数が危険なのがよくわかります。

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

+ `Int`は扱える桁に限りがあるぜ
+ `Integer`は動的にメモリを確保するので（メモリがある限り）上限はないぜ
+ `Float`と`Double`の違いは精度だけで、`小数`は有限のメモリでは表現できないので誤差が前提だぜ
+ `Rational`はすげーぜ、`分数`の形で値を扱うので`Rational`同士の計算においては誤差が出ないぜ

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

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