なんで言ってくれなかったの? オーバーフローしてたって...

> 前置きをスキップするときは「オーバーフローの検出」に進んでください。

F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

# 言ってくれないオーバーフロー
F#のデフォルト環境では、オーバーフローが起きても警告や例外を全く発しない場面があります。たとえばこんな計算。

```fsharp:これは何の問題もない
255 + 1 = 256
```
255に1を足したら256になるはずです。しかし、これを`byte`型に変えてみるとどうでしょうか。

```fsharp:結果は0
255uy + 1uy = 0uy    // (?!)
```

`255uy`に`1uy`を足すと結果は`0u`になってしまいます。これがオーバーフローの一例です。

計算以外でもオーバーフローを起こす場合があります。それはキャスト(型変換)です。値によっては正負が逆転してしまうこともあります。

```fsharp:キャストで正負が逆転
sbyte 384 = -128y
```

これでも、やはり警告や例外は出ません。こうした現象をわざと利用する場合もないことはないでしょうが、とはいえ「なんで言ってくれないの?」と思うこともあるのではないでしょうか。

そこで、ここではなぜオーバーフローが起こるのかを探り、それを検出する方法を紹介します。F#のバージョンは4.1([FSharp.Core](https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core "https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core") 4.4.1.0)とします。

# なぜオーバーフローするのか
オーバーフローは**桁あふれ**ともいいますが、設定しようとしている値が数値型で扱える範囲を超えてしまうことをいいます。F#では、定数で数値を表すときに`u`や`y`などの[接尾辞をつけられます](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/literals#literal-types "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/literals#literal-types")。これにより簡単に数値型を表せると同時に、オーバーフローの可能性も見通しやすくなります。ただし、これは数値の定数を表す場合に限られます。

F#では[型推論](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/type-inference "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/type-inference")のおかげで型名を書かなくて済むことも多いため、ソースコードを見ただけではオーバーフローが起こる可能性のある個所を見落としてしまうリスクがあります。結果を見て初めてオーバーフローしていたと気づく場面もあるかもしれません。

## 演算によるオーバーフロー

[byte型](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.byte-type-abbreviation-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.byte-type-abbreviation-%5bfsharp%5d")では、扱える数値の範囲が**0uy～255uyの整数**と決まっています。これは**符号なし8ビット**で表せる数値を表しています。この型でオーバーフローが起きるのは、結果が255を上回るときと、0を下回るときです。

```fsharp:byte型でのオーバーフローの例
255uy + 1uy = 0uy    // 結果が255を上回るときのオーバーフロー (本来なら256)
0uy - 1uy = 255uy    // 結果が0を下回るときのオーバーフロー (本来なら-1)
```

`255 + 1`の結果は本来256ですが、`255uy + 1uy`ではオーバーフローして`0uy`と値が小さくなってしまいます。2進法で表すと255は`0b1111_1111`となります(最下位ビットから上位に4桁ごとで区切っています)。これに1を加えると、本来なら`0b1_0000_0000`となるのですが、byte型は8ビットですから桁数が足りません(ここではビット数を2進数での桁数とします)。そのため最上位の桁はメモリに収まらず(桁あふれを起こして)、結果が`0uy`となるのです。

次の`0uy - 1uy`の結果は`255uy`で、上記とは逆に数値が大きくなります。これがなぜオーバーフローかというと、最上位よりも上位の桁から1を借りようとするからです。

> ただ、実際の引き算(-:減算)では**2の補数**(すべてのビットを反転させて1を足した値で、正負を反転させた値に相当)を足すという処理が行われるため、この計算では桁借り(繰り下がり)が起こりません(qiita内にも[2の補数についての説明がいくつかあるようです](https://www.google.co.jp/search?q=2の補数+site%3Ahttps%3A%2F%2Fqiita.com "https://www.google.co.jp/search?q=2の補数+site%3Ahttps%3A%2F%2Fqiita.com"))。

では`0uy - 1uy`はどう計算するかというと、これを`0uy + (-1)`ととらえ、`-1`を`1uy`の2の補数、つまり`~~~1uy + 1uy = 255uy`で置き換えます(`byte`型は符号なしなので負の数にはなりません)。よって`0uy - 1uy`は`0uy + 255uy`に置き換えられ、結果が`255uy`となるのです。

こうした処理は、定数と`uy`などの接尾辞が書かれていれば気づきやすいですが、関数内でどのような値がどう使われているかがわからなければ気づかないかもしれません。そうなると、オーバーフローが起こる可能性のある個所を特定するのが難しくなります。

## キャストによるオーバーフロー

今度はキャストによるオーバーフローを探っていきます。

F#の数値型は扱える範囲が異なるものが複数存在するため、範囲の広さや符号の有無によって、キャスト(型変換)の際に意図しない結果を生むことがあります。

> 以下はできれば行うべきでないキャストの例です。

ここでは[int16型](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.int16-type-abbreviation-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.int16-type-abbreviation-%5bfsharp%5d")から[sbyte型](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.sbyte-type-abbreviation-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.sbyte-type-abbreviation-%5bfsharp%5d")にキャストする場合を取り上げます。

```fsharp:キャスト(型変換)によるオーバーフローの例
sbyte  384s = -128y   // 正の数 → 負の数
sbyte -192s =  64y    // 負の数 → 正の数
```

`sbyte 384s = -128y`は`int16`(16ビット)から`sbyte`(8ビット)へのキャストです。どちらも符号ありの型なので負の数を扱えますが、キャストによってこのように正負が逆転してしまうことがあります。扱える数値の範囲が広い型からそれが狭い型に変換されるときは、上位のビットが削られます。`384`は2進法で`0b1_1000_0000`と表されますが、下位8ビットだけを見ると`0b1000_0000`です。これを`sbyte`型では`-128y`として扱います(最上位ビット1は負の数を表すため)。よって`sbyte 384s = -128y`となり、正負が反転してしまうのです。

一方、`sbyte -192s =  64y`ですが、`-192s`は2進法で表すと`0b1111_1111_0100_0000s`です。下位8ビットだけに注目すれば`0b0100_0000`で、これが結果の`64y`です(最上位ビット0は正の数を表す)。よって、この場合も正負が逆転してしまうのです。

ここまでオーバーフローの原因として演算とキャストを取り上げましたが、こうした処理が行われても、F#のデフォルトでは警告も例外も発してくれません。ですが、F#ではこれを検出する方法が提供されていますので、これを使えばオーバーフローが起きた時でも対応できるようになります。

# オーバーフローの検出

F#における演算とキャストによるオーバーフローを検出するには[Microsoft.FSharp.Core.Operators.Checked](https://msdn.microsoft.com/visualfsharpdocs/conceptual/operators.checked-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/operators.checked-module-%5bfsharp%5d")モジュールを設定します。元に戻すには[Microsoft.FSharp.Core.Operators](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.operators-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.operators-module-%5bfsharp%5d")モジュールを設定し直します。

```fsharp:オーバーフローを検出する設定
open Microsoft.FSharp.Core.Operators.Checked  // を実行
```
```fsharp:設定を戻す場合
open Microsoft.FSharp.Core.Operators  // を実行
```

この[Checked](https://msdn.microsoft.com/ja-jp/library/ee340296(v=vs.120).aspx "https://msdn.microsoft.com/ja-jp/library/ee340296(v=vs.120).aspx")モジュールが設定されると、上記のような演算およびキャスト(型変換)によってオーバーフローが起きたときに`System.OverflowException`が発生するため、オーバーフローを検出できるようになります。そのため、たとえばオーバーフローが発生する可能性がある処理を行うときは結果を`option`型にするといった対応もとれます。

```fsharp:オーバーフローを検出する可能性がある処理の結果をoption型にする
open Microsoft.FSharp.Core.Operators.Checked    // オーバーフローを検出する設定

// オーバーフローが起こる可能性がある処理
let f x = x + 1uy

// オーバーフローの検出を考慮して結果をoption型にする
let tryOption f x =
    try Some(f x) with
    | :? OverflowException -> None
    | _ -> reraise()

tryOption f 255uy  // None     (オーバーフローを検出した)
tryOption f 1uy    // Some 2uy (オーバーフローを検出しなかった)
```

この設定で対応できる演算子は以下のとおりです。

* 掛け算 - (*)
* 足し算 - (+)
* 引き算 - (-)
* マイナス(正負の反転) - (~-)

```fsharp:オーバーフローで例外発生
open Microsoft.FSharp.Core.Operators.Checked

// 以下はすべて例外発生「System.OverflowException: 算術演算の結果オーバーフローが発生しました。」
128uy * 3uy
255uy + 1uy
-128y - 1y
```

同じくこのモジュールで対応できるキャストの型は以下のとおりです。

* byte, sbyte
* char
* int, int16, int32, int64
* uint16, uint32, uint64
* nativeint, unativeint

```fsharp:オーバーフローで例外発生
open Microsoft.FSharp.Core.Operators.Checked

// 以下はすべて例外発生「System.OverflowException: 算術演算の結果オーバーフローが発生しました。」
byte    256
uint16 -128y
```

# デフォルトでオーバーフロー時に例外発生する処理
ここまで、F#におけるオーバーフローの例と、それを検出する方法を取り上げましたが、上記の設定をしなくてもオーバーフローによる例外が発生する処理もありますので、最後にその例を紹介しておきます。

```fsharp:デフォルトでもオーバーフローによる例外が発生する例
pown 2 31                         // 例外発生「System.OverflowException: 算術演算の結果オーバーフローが発生しました。」
System.Decimal.MaxValue + 0.5M    // 例外発生「System.OverflowException: Decimal 型の値が大きすぎるか、または小さすぎます。」
```

F#を使う皆さんの参考になれば幸いです。