離散的な周期函数について


Source code を入れない代わりに (と言ってはなんですが) 解説をしましょう。

日付というのは, 1.5 日目とか, 2/3 日目とかいう半端な日数は (無理やり考えた場合を除き) 普通は考えないわけで, このように1, 2, 3 ... という整数値しか取らないような場合を数学では「離散的 discrete」と言います。

曜日やここで示した干支などはある日数が経つと同じものが巡って来ます。曜日は 7 日で戻ってくるので 7 日が周期 period である, といい, 干支は 60 日 (又は 60 年) で戻ってくるので 60 日 (又は 60 年) が周期であるわけです。ある周期で同じ値を返す函数 (かんすう) のことを周期函数 periodic function といいます。

私の作った函数では十干と十二支とを別々に計算して後で合わせていますので, 親しみのあるであろう十二支の方で説明します。

今年 (西暦 2000 年) は辰年です。2001 年は巳年, 2002年は午年, ... というように進んでいって, 2011 年が卯年, そして 2012 年が再び辰年になるわけです。2000 を 12 で割ると商が 166 余り 8; 2012 も 12 で割ると商が 167 余り 8, というわけで, 12 で割ったときの (商ではなくて) 余りが 8 の年はいつでも辰年であるということが分かります。同様にして 12 で割ったときの余りが 9 ならば巳年, 10 ならば午年, ... という具合になるわけなのです。

年の方は上記のように簡単ですが, 日のほうはとっても大変です。何故かというと, つい最近 (2000 年の) 2 月 29 日問題が起こって分かったと思いますが, 閏年と言うものが入ってくる上に, 閏年を入れる規則が 400 年周期であるということが計算を難しくしているわけです。

大分昔に作って忘れてしまったので, 一寸 source code を見てみましょう。(^_^;


public static int dJuunishi(int y, int m, int d)
{
	if(m < 3){
		y--;
		m += 12;
	}
	return (int)((subDate(y) + (66 * m + 6) / 10 + d) % 12);
}

private static long subDate(long y)
{
	return 5 * y + y / 4 - y / 100 + y / 400;
}

Original が C 言語が元だったので, とても JAVA の code とは思えない感じもします。 最初の if 文は何をやっているのかというと閏日 (29th Feb) は 2 月末日にしか起こらないので, 1, 2 月は前年の 13 月, 14 月と考えて計算してもまったく困らないことから, そのように計算しなおしているわけです。

普通の人が見ると return 文がとても謎だと思います。ここを解説していきましょう。一寸十干の方の計算と共用の為 下請けの函数に出しているので分かりにくくなっているのでそこだけ取り出して
(int)((5 * y + y / 4 - y / 100 + y / 400 + (66 * m + 6) / 10 + d) % 12)
の形で見てみましょう。

下請け函数が long で値を返しているのは, overflow を避けるためです。(あまり意味がないかもしれませんが)。JAVA は型 check がうるさいので (int) で cast しています。後ろの方にある % 12 は, 年のときと同じで, 十二支だから 12 で割った余りを求めているのです。

平年 (閏年でない年) は良く知られているように 365 日です。365 を 12 で割ると余りが 5. ということは十二支は一年ごとに 5 だけずれていくので, 5 * y を計算しているわけです。閏年の場合は 1 日これに加えなければならないので y / 4 (年を 4 で割ったときの整数の商) を加えると, 4 年経つと 1 だけ増えていくことになるわけなのです。しかし 100 で割り切れる年は平年にしなくてはいけないので, 同様に考えて y / 100 を引き, 更に, 400 で割り切れる年は例外として閏年にしなくてはいけないので y / 400 を加えているのです。

一番問題なのは (66 * m + 6) / 10 + d という謎の式ですね。この函数は子年に 0 を返すように設計されています。2000 年の 3 月 1 日は「戊(つちのえ)午の日」らしいです。午は 6 という値を返すようにしなくてはいけません。「12 で割った余り」という点を除けば, 日が経つにつれ一つずつ後ろに行くのですから + d は不思議ではありませんよね。問題は (66 * m + 6) / 10 という所ですよね。ここは非常に苦労したところで, 細かいところを省くと, ここを ((66 * m + 6) / 10) % 12 と見直していただいて, 3/1 を基準としてみたとき, 4, 5, 6, ... の各月が 3/1 から見て何日ずれているかを調べているのです。ここの計算は Microsoft Excel® を用いて試行錯誤の結果求めました。もう大分前なので何故この値にしたのかは忘れてしまいました。(^_^;;;

こんなんでお分かりいただけたでしょうか ?

このように離散的周期函数はその周期が p ならば (計算式) % p でだいたい実装することが出来ます。

一寸だけ難しいことを言わせてもらいますと, これはグラフが universal covering である xy 平面から % p という計算によって, flat torus 上の函数に「翻訳」されているということです。

最後に分からなかったことを幾つか:
 干支と同様に「六曜」っていうのがありますよね。大安とか, 仏滅とかいうやつです。あれも計算させようと思ったんですが, 旧暦と関係している上に, 閏月をどうやって入れるかというような規則が分からなかったので program 組めませんでした。(尚, 六曜廃止運動というのもあるらしいので出来なくて良かったのかもしれない。)
 他にも国民の祝日もいれてもいいかなとも思ったのですが, 実は秋分の日と春分の日がどう定められているのか計算方法がわからず頓挫しております。Microsoft Outlook 2000® なんかでは当たり前のように計算しているので何らかの計算方法があると思われるのですが, 全然手付かずです。どなたかご存知の方がいたらご教授願いたい。(こちらまで)

Wednesday, 15th March, 2000.


100c + y 年 m 月 d 日の曜日が
[21c/4] + [5y/4] + [26(m + 1)/10] + d - 1 (mod 7)
(0 が日曜) となるというのは 1887 にゼラーが考えたということだ。

藤村幸三郎, 田村三郎: 数学歴史パズル
---数学者も頭をひねった 75 問
講談社ブルーバックス, B592, 1985.

Tuesday, 30th January, 2001.


参考文献:
フィンローダ 疑問解決 ! 初級 C 言語 Q & A (第 15 回) 凝ったアルゴリズム, C magazine (8), 1996, ソフトバンク


BACK