うるう年(閏年)とは
うるう年(閏年)は1年が366日ある年です。そうでない年は平年です。
グレゴリオ暦によるルール
- 西暦年が4で割り切れる年は(原則として)閏年。
- ただし、西暦年が100で割り切れる年は(原則として)平年。
- ただし、西暦年が400で割り切れる年は必ず閏年。
うるう年を計算する関数について
Pythonのcalendarモジュールのisleap()
関数は、下記のように定義されています。
def isleap(year):
"""Return True for leap years, False for non-leap years."""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
さて、ここで2つの疑問がわきました。
- なぜ、ルールはわかりにくく例外を重ねる書き方をしているのか?
- なぜ、Pythonのコードはルールの順番通りに書いてあるのか?
それぞれ考察してみましょう。
なぜ、ルールはわかりにくく例外を重ねる書き方をしているのか?
ルールの英語の記述は、下記のようです。
Gregorian leap year rule is: Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400.
例外を重ねる書き方は、この訳と思われます。
また、このルールの順番は、暦年の平均の長さの精度を高めていく順番と思われます。
- 暦年の平均の長さ ≒ 365.24218944日
- ルールの1まで:365.25日
- ルールの2まで:365.24日
- ルールの3まで:365.2425日
また、この順番は可能性の高い順と見ることもできます。
なぜ、Pythonのコードはルールの順番通りに書いてあるのか?
Pythonのand
やor
は、短絡評価されます。短絡評価とは、下記のことです。
-
式1 and 式2
で、式1の判定が偽であれば式2を評価しない。 -
式1 or 式2
で、式1の判定が真であれば式2を評価しない。
判定する年に偏りがないものと仮定すると、短絡評価により下記が最も効率のよい書き方になります。
year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
理由を説明します。
3/4は確実に平年です。したがって、year % 4 == 0
が偽かどうかを見ることで3/4は1回の評価で済みます。
残りの1/4を考えます。このとき24/25は確実にうるう年です。したがって、year % 100 != 0
が真かどうかを見ることで全体の24/100は2回の評価で済みます。
残りの1/100は3回の評価で決定します。
つまり、可能性の高い順に評価することで、短絡評価により効率的になります。
具体的に2000年から2399年までで確かめると、下記のように評価回数が最小になっています。
式 | 評価回数 |
---|---|
year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) |
504 |
year % 4 == 0 and (year % 400 == 0 or year % 100 != 0) |
599 |
(year % 100 != 0 or year % 400 == 0) and year % 4 == 0 |
801 |
(year % 400 == 0 or year % 100 != 0) and year % 4 == 0 |
1196 |
year % 4 == 0 and year % 100 != 0 or year % 400 == 0 |
804 ※ |
※ ちなみに、表の最後のように括弧がなくても(今回は)ルール通りですが、効率的ではありません。
2つの疑問を考えることで、ルールとコードに共通する理由が見えてきました。
以上