TL;DR
離散量(整数型や時刻を含まない日付型)の場合は、[closed, closed]
が優位である。
連続量(floatや秒未満の精度の日時型)の場合は、[closed, open)
が優位である。
どちらを選ぶにせよ、終端を含むかは必ずドキュメントに明記すべきである。
1982年のエドガー・ダイクストラによる説明
a, bは非負整数とする。
- [a, b)
- (a, b]
- [a, b]
- (a, b)
0以上の区間を表すために、2と4だとa = -1にしなければならない。非負整数のみを考えていたのに負数を使わなければならない。
空の区間を表すために、3だと[1, 0]や[2, 1]のようにa > bにしなければならない。これは見苦しい。
よって残る1が良い。
Xerox PARCにおいて開発されたMesa言語には、[a, b]
[a, b)
(a, b)
(a, b]
の4種類の方法で区間を表す記法があったそうだ。しかし[a, b)
以外はバグを生じがちなので非推奨になったという。
上記の説明への反論
上記では定義域を非負整数としていたが、コンピュータで扱える整数には上限がある。[a, b)だと、最大値を含む区間を表せない。
例えば定義域がuint8の場合、0 <= x <= 255を[a, b)で表すには[0, 256)としなければならないが、256はuint8に属さないのでこれはできない。
もしnullが使えるなら[0, null)でx >= 0を表せる。
[a, b]ならuint8の任意の区間を表せる。空の区間は確かにa > bになり見苦しいが、許容できる。
連続量を分割する場合は[closed, open)にすべきである
例えば1日を1時間ごとに分割したい場合、
[closed, open)なら[0,1), [1,2), [2,3), [3,4), ..., [23,24)
とできる。
[closed, closed]だと[0,0:59:59], [1, 1:59:59], ... [23,23:59:59]
としなければならない。
これは秒単位という精度情報をハードコーディングしてしまっていることになる。
もしミリ秒単位にしたくなったら、[0,0:59:59.999], [1,1:59:59.999], ... [23,23:59:59.999]
に書き換えなければならない。
時間に限らず、実数を扱う場合は同様である。
付録:区間の表記法
数学では [a, b]
と (a, b)
のように角括弧と丸括弧で端点の包含を区別するのが一般的だが、プログラミングにおいてはこれらの表記は他の意味で使われることも多く、まぎらわしい。そこで、他の表記法が用いられることがある。
Unityでは
[minInclusive..maxInclusive]
[minInclusive..maxExclusive)
のように、カンマの代わりに ..
を用いることで、配列の表記等と区別しているようだ。良いと思う。
終端のみの区別になってしまうが、各言語で次のような表記法がある:
Rust: 1..5
は [1, 5)(右端を含まない)
Rust: 1..=5
は [1, 5](両端を含む)
Swift: 1..<5
は [1, 5)
Ruby: 1..5
は [1, 5]
Ruby: 1...5
は [1, 5)
付録:「取りうる値の中で最大のもの」でないならmax exclusiveと呼ぼう
数学において、最大値(maximum)とは「集合に属する要素のうち最大のもの」を意味する。a, bを実数としたとき、実数の部分集合 [a, b)
は最大値なしとなる。bを最大値やmaxと呼ぶのは誤りである。
なので、[min, max)
という表記は好ましくなく、 [min, maxExclusive)
とすると良い。もちろん min側も明示して [minInclusive, maxExclusive)
でも良い。
また、「パラメータAに指定可能な最大値は10である」と書いてあれば10は許容される。
// Bad
for (i = 0; i < MAX; i++)
という書き方も、MAXは「iの取りうる値の中で最大のもの」ではないので好ましくなく、
// Good
for (i = 0; i < MAX_EXCLUSIVE; i++)
と書く方が誤解の余地が無い。