gcd(80, n) > gcd(50, n) でうるう年が分かるらしい
gcd(80, n) > gcd(50, n) でうるう年が分かるらしい
面白いツイートを見つけました。
Lynnさんのツイートです。
I don't know who needs to hear this but year "n" is a leap year iff
— Lynn (@chordbug) 2021年3月22日
gcd(80, n) > gcd(50, n)
簡単にいうと、「 が成り立つとき、 はうるう年である」ということです。
本当に?
そもそもうるう年とは?
うるう年とは2月29日が存在する年で、うるう年である年の定義は以下のようになります。
- 西暦年が4の倍数のときはうるう年。
- ただし、西暦年が100で割り切れる年は平年(つまりうるう年でない)。
- ただし、西暦年が400で割り切れる年は必ずうるう年。
ただし書きが2回も連続してうざいですね。
一応、わかりにくいという方のために、JavaとHaskellでうるう年の判定をする関数のソースコードを書いたので貼っておきます。
Java
public class Main{ public static boolean isLeapYear(int n) { if (n % 400 == 0) return true; else if (n % 100 == 0) return false; else if (n % 4 == 0) return true; else return false; } public static void main(String[] args) { System.out.println(isLeapYear(1900)); System.out.println(isLeapYear(2000)); System.out.println(isLeapYear(2001)); System.out.println(isLeapYear(2004)); } }
出力結果:
false true false true
Haskell
isLeapYear n | n `mod` 400 == 0 = True | n `mod` 100 == 0 = False | n `mod` 4 == 0 = True | otherwise = False main = do print $ isLeapYear 1900 print $ isLeapYear 2000 print $ isLeapYear 2001 print $ isLeapYear 2004
出力結果:
False True False True
最大公約数ってなんだっけ?
最大公約数は小学校で習うのですが、僕は「小学校で習ったから覚えてるよね?」と威圧する風潮が大嫌いなので*1、ここで説明します。
例えば、12と18について
12は、1か2か3か4か6か12で割り切れます。 これは「12は、1の倍数で、2の倍数で、3の倍数で、4の倍数で、6の倍数で、12の倍数である」とも言えます。
18は、1か2か3か4か6か9か18で割り切れます。 これは「18は、1の倍数で、2の倍数で、3の倍数で、4の倍数で、6の倍数で、9の倍数で、18の倍数である」とも言えます。
つまり、12と18は、どちらも、1の倍数と2の倍数と3の倍数と4の倍数と6の倍数であるということになります。 このとき、1と2と3と4と6を「12と18の公約数」と言います。 そう、公の約数ですね。
えっなんで「"共"約数」じゃないのかって? 多分、英語名のcommon divisorを訳したら「公約数」になっちゃったんじゃないんですか?(想像)
さて、話を戻しましょう。
公約数たちの中で一番大きい公約数を最大公約数と言います。
つまり、先程の例だと1と2と3と4と6の中で6が一番大きいので、「12と18の最大公約数」は6です。 これを数式で、 というふうに表記します。
なんとなく2000年代の周りを調べてみる
nが1990~2030のときのgcd(80, n)の値です。
一方こちらは、nが1990~2030のときのgcd(50, n)の値です。
これらを重ね合わせると……
オレンジの部分が見えるところが、オレンジ()が青色()より大きい部分です。 たしかに、うるう年と一致している……?
うーん、ちゃんと証明しましょう。
さあ証明の時間だ
もっとエレガントが方法があるかもしれません。 (あったら教えて)
あと数学は得意ではないので、 厳密な証明とかできません。 そこのところはご了承ください。
nが400である倍数のとき
まずはがの倍数であるときのことを考えてみましょう。
の倍数である数をとします。
なので、
必ず になります。
また、
なので、
必ず になります。
つまり、がの倍数であるとき
必ず となります。
nが400の倍数ではないが、100の倍数であるとき
次にがの倍数ではないが、の倍数であるときのことを考えてみましょう。
の倍数ではないが、の倍数である数をとします。
なので、
必ず になります。
ところで、
とを素因数分解すると
となります。
つまり、 は、で最大回もしくは回割り切れる必要がありますね。
(これはどういうことかというと、がで割り切れる回数が回より少ないとの倍数でなくなってしまい、逆にで割り切れる回数が回以上になるとの倍数になってしまうということです。)
は では最大回までしか割り切れず、
は、で最大回までしか割り切れないため、
必ず になります。
つまり、がの倍数ではないが、の倍数であるとき
必ず となります。
nが100の倍数ではないが、4の倍数であるとき
最後にがの倍数ではないが、の倍数であるときのことを考えてみましょう。
の倍数ではないが、の倍数である数をとします。
とを素因数分解すると
となります。
つまり、 は、で最大回もしくは回割り切れる必要がありますね。
(で割り切れる回数が回以上になるとの倍数になってしまいます。)
は では最大回までしか割り切れず、
が、の倍数でない場合、必ず になります。
が、の倍数である場合、必ず になります。
一方で
なので、
が、の倍数でない場合、必ず になります。
が、の倍数であった場合、必ず になります。
つまり、がの倍数ではないが、がの倍数であるとき 必ず となります。
nが4の倍数でないとき
(追記です。)
見事にこの証明をするのを忘れてました……
がの倍数でないのことを考えてみましょう。
の倍数でない数をとします。
がで回割り切れ、で回割り切れるとき、となります。
がで回割り切れ、で回割り切れるとき、となります。
がで回以上割り切れ、で回割り切れるとき、となります。
がで回割り切れ、で回割り切れるとき、となります。
がで回割り切れ、で回割り切れるとき、となります。
がで回以上割り切れ、で回割り切れるとき、となります。
といわけで、がの倍数でないとき 必ず となります。
まとめ
- がの倍数であるとき
- がの倍数ではないが、の倍数であるとき
- がの倍数ではないが、がの倍数であるとき
- がの倍数ではないとき
見事にうるう年の定義と一致していますね。
コードの書き直し
ソースコードを書き直してみました。
Java(改)
public class Main{ public static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } public static boolean isLeapYear(int n) { return gcd(80, n) > gcd(50, n); } }
Javaには最大公約数を求める関数がないので、自前で定義する必要があります。 とはいえ、ユークリッドの互除法と再帰を使って一行で書けます。
Haskell(改)
isLeapYear n = gcd 80 n > gcd 50 n
うん、すっきりしましたね。
ちなみにコードを短くすることはできますが、 パフォーマンス的にはあまりおいしくないと思うので コードゴルフでもしない限り、 普通に書いたほうがいいんじゃないでしょうか。
*1:半分冗談ですよ、半分本気ですけど。あと、わざわざここで説明するのは、日本語の「最大公約数的に」という言葉の使い方について考えてほしいというねらいもありますが、こんなことでここに書くべきことじゃないかもしれませんね。