13日の金曜日って1年に何回ぐらい?

# 1. 概要

要するにタイトル通りです。

何かを計算するのが大好きなので、毎月13日になる度に考えてしまいます。
Quiita に投稿するような内容なのかとか思いましたが、たまたま今日 (投稿日) がせっかくの13日の金曜日だし、c# でプログラムも作ったし、いいかなーと…

お目汚しで申し訳ないです。

# 2. 計算方法

経験上、13日の金曜日は定期的にあるわけでもなく、1 年あたりの回数も特に一定ではありません。
なので、ある母数`N`をとって **「`N`日間に13日の金曜日は何回あるか」** を計算することにします。

この母数 `N` に求められる条件は以下のようなものになります。
- 一週間の日数 (つまり 7) の倍数であり、かつ
- うるう年の計算方法の最大周期 (日数) の倍数であること。

よく知られている通り、うるう年の計算方法は以下のようなものになります。
1. 西暦の年号が 400 で割り切れる年はうるう年である。
2. 上記以外で 100 で割り切れる年はうるう年ではない。
2. 上記以外で 4 で割り切れる年はうるう年である。

ここからわかるように、うるう年の最大周期は 400 年です。
つまり、400 年間の日数と 7 の最小公倍数を母数 `N` にすれば、月・日・曜日の組み合わせのパターンは `N` 日周期なので、何千年何万年後であろうが `N` 日間の 13 日の金曜日の回数を数えさえすれば十分ということになります。

で、この母数 `N` を求めて、その間の13日の金曜日の回数を数えるプログラムがこちらです。

```c#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;

namespace Experiment.Friday13th.CUI
{
    internal sealed class Program
    {
        private const int _BASE_YEAR = 2000;    // 起点となる年
        private const int _N_YEARS = 400;       // うるう年が一周する周期(年)
        private const int _DAYS_OF_WEEK = 7;    // 一週間の日数

        private static void Main()
        {

            // _N_YEARS が何日分に相当するかを求める
            var days =
                (int)(
                    new DateTime(_BASE_YEAR + _N_YEARS, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                    - new DateTime(_BASE_YEAR, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalDays;

            // 年月日と曜日が一致する周期(日数)を求める
            var gcm = (int)BigInteger.GreatestCommonDivisor(days, _DAYS_OF_WEEK);
            Debug.Assert(days % gcm == 0);
            var totalDays = days / gcm * _DAYS_OF_WEEK;

            // 年月日と曜日が一致する周期(年数)を求める
            Debug.Assert(totalDays % days == 0);
            var totalYears = _N_YEARS * (totalDays / days);

            var count = 0; // totalYears 年間の13日の金曜日の回数
            var count_0 = 0; // totalYears 年間で、13日の金曜日が1日もない年の回数
            var count_1 = 0; // totalYears 年間で、13日の金曜日が1日だけある年の回数
            var count_2 = 0; // totalYears 年間で、13日の金曜日が2日だけある年の回数
            var count_3 = 0; // totalYears 年間で、13日の金曜日が3日だけある年の回数
            var count_4 = 0; // totalYears 年間で、13日の金曜日が4日以上ある年の回数

            // totalYears 年間の13日の金曜日をすべて洗い出す
            for (var year = _BASE_YEAR; year < _BASE_YEAR + totalYears; ++year)
            {
                var dates = new List<string>();
                var n = 0;
                for (var month = 1; month <= 12; ++month)
                {
                    var d = new DateTime(year, month, 13, 12, 0, 0, DateTimeKind.Utc);
                    if (d.DayOfWeek == DayOfWeek.Friday)
                    {
                        ++count;
                        ++n;
                        dates.Add($"{month}月13日");
                    }
                }

                switch (n)
                {
                    case 0:
                        ++count_0;
                        break;
                    case 1:
                        ++count_1;
                        break;
                    case 2:
                        ++count_2;
                        break;
                    case 3:
                        ++count_3;
                        break;
                    default:
                        ++count_4;
                        break;
                }

                Console.WriteLine($"{year} 年の13日の金曜日: {string.Join(", ", dates)}{(DateTime.DaysInMonth(year, 2) > 28 ? " (うるう年)" : "")}");
            }

            Console.WriteLine($"以降、{totalYears:N0} 年周期で繰り返します。");
            Console.WriteLine("-----");
            Console.WriteLine($"うるう年を考慮すると、年月日と曜日の対応は {totalYears:N0} 年で一周します。これは {totalDays:N0} 日間であり、{totalDays / _DAYS_OF_WEEK:N0} 週間です。");
            Console.WriteLine($"13日の金曜日は平均して年に {(double)count / totalYears:F2} 回あります。");
            Console.WriteLine($"任意の月の13日が金曜日である確率は {(double)count / (totalYears * 12):P2} です。(参考: 1/7 = {1.0 / 7.0:P2})");
            Console.WriteLine($"13日の金曜日がない年は全体の {(double)count_0 / totalYears:P2} です。");
            Console.WriteLine($"13日の金曜日が1日だけある年は全体の {(double)count_1 / totalYears:P2} です。");
            Console.WriteLine($"13日の金曜日が2日だけある年は全体の {(double)count_2 / totalYears:P2} です。");
            Console.WriteLine($"13日の金曜日が3日だけある年は全体の {(double)count_3 / totalYears:P2} です。");
            Console.WriteLine($"13日の金曜日が4日以上ある年は全体の {(double)count_4 / totalYears:P2} です。");

            Console.WriteLine("ENTERキーを押すと終了します。");
            Console.Beep();
            _ = Console.ReadLine();

        }
    }
}
```

# 3. 計算結果

上記のプログラムを実行して、以下のことが分かりました。
- 400 年間の日数は 146,097 日である。この値はたまたま 7 の倍数なので、146,097 日間 ( = 20,871 週間 = 400 年間) を母数とすればよい。
- 13日の金曜日は平均して年に約 1.72 回ある。
- 任意の年・月の 13 日が金曜日である確率は約 14.33% である。(参考: 1/7 = 約 14.29%)
- 13日の金曜日がない年はない。
- 13日の金曜日が 1 日だけある年は全体の約 42.75% である。
- 13日の金曜日が 2 日だけある年は全体の約 42.50% である。
- 13日の金曜日が 3 日だけある年は全体の約 14.75% である。
- 13日の金曜日が 4 日以上ある年はない。

ちなみに、13日の金曜日が年に 3 回あるのは、何故か以下のパターンのみです。
- うるう年の場合
1月13日, 4月13日, 7月13日
- うるう年ではない場合
2月13日, 3月13日, 11月13日

こういうのを総当たりではなくて整数論辺りで証明できると楽しいと思うんですが、私のおつむでは無理そうです。無念。

# 4. おまけ

- [ソースコードのリポジトリ](https://github.com/rougemeilland/Experiment.Friday13th/tree/main
)
- [完全な出力結果](https://github.com/rougemeilland/Experiment.Friday13th/blob/main/Experiment.Friday13th.CUI/output.txt
)