[再考] 範囲に終端は含むべきか？（[a, b]と[a, b)どちらが良いか？）

プログラミングで範囲を扱う際、下記のinclusiveとexclusive、どちらが良いか？

```
inclusive  [a, b]  a <= x <= b
exclusive  [a, b)  a <= x < b
```

以前に[なぜ区間は\[closed, open)での半開区間で表現すべきか？](https://qiita.com/aosho235/items/c3fcdad22c2c059b7741
)という記事でダイクストラの意見を紹介したが、最近どうもinclusiveの方が良いのではないかという気がしており、ちょっと再考してみた。

本記事では以下の観点から考察する。
1. 一般的に使われるのはどちらか
1. 重なりの無い範囲に分割できるか
1. 空の範囲の表現
1. 全体の表現

考えるデータ型は整数、浮動小数点数、PostgreSQLのtimestamp型（精度マイクロ秒）とする。

※PostgreSQLのtimestamp型は、内部では2000-01-01 00:00からの経過マイクロ秒を64ビット整数で保持している。デフォルトでは小数点以下6桁（マイクロ秒）の精度だが、`timestamp(N)`のように定義することでN桁の精度を指定できる。N=0にすると秒単位となり、`2000-01-01 00:00:00.123456` を登録しようとすると`2000-01-01 00:00:00` に丸められる。

## 1. 一般的に使われるのはどちらか

日常生活で使われる範囲はほぼinclusiveと言っていいだろう。ゲームなどで「10〜20ポイント」といったら20を含むし、「1月1日〜1月31日」と書いたら31日ｗ含むし、「0:00〜23:59」のように書いたら23:59は23:59:00ちょうどではなく分の精度で考えて23:59を含むという意味であり、23:59:59もこの範囲に含まれると解釈されるだろう。

数学では、実数の範囲はexclusiveで表現すると高校で教わった人も多いだろう。
これは、[a, b] [b, c]というようにinclusiveだと、bが両方の範囲に含まれてしまって都合が悪いからである。[a, b) [b, c)にすれば重なりのない範囲に分割できる。

プログラミングにおいて、配列のスライスはexclusiveであることが多い。[JavaScript](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)、 [Python](https://docs.python.org/ja/3.13/tutorial/introduction.html)、 [Go](https://go-tour-jp.appspot.com/moretypes/7)

UnityのRandom.Rangeは、なんと[intはexclusiveだが、floatはinclusive](https://docs.unity3d.com/ja/2021.1/ScriptReference/Random.Range.html
)である。（何か理由があるのだろうか？分かる人がいたら教えて下さい）

```
public static int Range (int minInclusive, int maxExclusive);
public static float Range (float minInclusive, float maxInclusive);
```

SQLのBETWEENはinclusiveである。

## 2. 重なりの無い範囲に分割できるか

前述のように、数学で実数の範囲を分割する際はexclusiveが一般的である。
では、整数、浮動小数点数、timestamp型の場合はどんな問題があるのだろうか？

### 整数の場合

[0, 9]を真ん中で分割すると [0, 4] [5, 9] になる。
[0, 10)を真ん中で分割すると [0, 5) [5, 10) になる。

どちらも特に問題は見当たらない。

[a, b)の長さはa - bだが、
[a, b]の長さはa - b + 1となることに注意。exclusiveの方が少しだけシンプルである。配列のスライスがexclusiveなのはこれが主な理由だと思われる。

### 浮動小数点数の場合

exclusiveの場合は特に問題がない。

inclusiveだと、数学の実数と同様の問題がある。
[0.0f, 1.0f] を[0.0f, 0.5f] [0.5f, 1.0f] に分割すると0.5fが両方に含まれてしまう。
実際には浮動小数点数は有限精度だから0.5fの「隣の数」が存在し、[0.0f, 0.5f] [0.5fの右隣の数, 1.0f]と分割すれば重なりは無くなるが、隣の数を求めるのが難しく、現実的ではない（ChatGPTに聞くとアルゴリズムを出してくれた）。

### timestamp型の場合

timestamp型の範囲は、以下のように、指定期間に含まれる行を取得する際によく現れる：

```sql
SELECT * FROM items WHERE '2000-01-01 00:00:00' <= createad_at AND created_at < '2000-01-01 01:00:00';
```

[0:00:00, 1:00:00] [1:00:00, 2:00:00]という分割だと、1:00:00の行が両方に含まれ、二重取得になってしまう。
[0:00:00, 0:59:59] [1:00:00, 1:59:59]という分割だと、0:59:59.000001のような値を取りこぼしてしまう。もっとも、データ登録時に必ず秒単位に丸めてから登録するようにすれば、問題はない。
[0:00:00, 0:59:59.999999] [1:00:00, 1:59:59.999999]という分割にすれば、二重取得も取りこぼしもない。

### 重なりがあっても問題ないか？

[0:00:00, 1:00:00] [1:00:00, 2:00:00]という分割で二重取得になっても、アプリケーション側の処理で片方から1:00:00の行を除外できるなら、一応問題は解消する。

## 3. 空の範囲の表現
inclusiveの場合、a > bなるa, bを用いて、[a, b]とすれば良い。例：[1, 0]
exclusiveの場合、任意のaを用いて[a, a)で良い。

## 4. 全体の表現

符号なし8ビット整数の範囲全体を表したいとする。

inclusiveなら[0, 255]で特に問題ない。
exclusiveなら[0, 256)となる。256は8ビット整数に収まらなくなってしまう。[0, 0)だけは全体を表す、とでも約束しておけば8ビット整数で表現できるが、そういった特殊ルールが必要。

## まとめ
観点|inclusive|exclusive
---|---|---
日常で一般的|◯|×
プログラミングで一般的|◯|◯
整数範囲の分割|◯|◯
実数範囲の分割|×|◯
空範囲の表現|◯|◯
全体の表現|◯|△

浮動小数点数で重なりの無い範囲に分割する必要がある際だけはexclusiveにする必要がある。
しかしそれ以外ではinclusiveの方がメリットが多いように思われる。
timestamp型は内部では整数であるし、範囲に重なりがあってもなんらか工夫で解決できないか考えてみるとよさそうだ。

いずれにせよ、範囲を扱うときはinclusiveなのかexclusiveなのか、必ずドキュメント化すべきだろう。

[a, b]という記法は配列と紛らわしいが、Unityでは
```
[minInclusive..maxInclusive]
[minInclusive..maxExclusive)
```
のように、カンマの代わりに `..` を用いているようだ。良いと思う。

コメント歓迎。