日本の祝日「敬老の日」は、9月15日~21日のうちの月曜日らしい。 21日は秋分でもありうるが、秋分の日も祝日だ。
法律上どう解釈されるのかは分からないが、 9月21日が秋分になり、月曜日だと、敬老秋分の日という2重休日になる。 あくまで推算で、絶対ではないが、 どうも、2876年 9月21日に発生し、 次に、3200年代に4回あって、3300年以降はますます増えると思われる。
「回帰年」とは、簡単に言えば「季節の移り変わりの上での1年」、 要するに、地球が太陽のまわりを1周する所要時間のことだ。
一方、カレンダーの1年は365日または366日である。
日というのは地球の自転周期で、年というのは地球の公転周期で、両者はまったく無関係の現象なので、 365や366といった整数できっかり割り切れるわけもなく、 現在の暦では、 うるう年をうまく混ぜることで、平均してカレンダーの1年が「回帰年」とほぼ一致するように工夫している。
それでも完全には一致しないし、平均してほぼ一致していても、 それは平均であって個々の例ではかなりばらつきがある。 そのため、現実の季節の変化に関連するイベント(春分・秋分など)のタイミングは、 計算上のカレンダーの日付の上を、複雑に移動することになる。
では、この変動パターンを具体的に調べていこう。
春分から次の春分(または秋分から次の秋分)の周期である回帰年は、最近の値では、平均して、 約365.24219日なので、春分の時刻は、前の年の春分の時刻に比べて:
上記は平均値で、実際には細かい変動がたくさんあるが、 平年には毎年6時間弱、遅れるのがミソだ。 そして、うるう年には約18時間巻き戻る。
秋分の推算値 日本時間 2004年 9月23日 1時29分52秒2 2005年 9月23日 7時23分07秒2 2006年 9月23日 13時 3分21秒9 2007年 9月23日 18時51分16秒3 2008年 9月23日 0時44分30秒6 2009年 9月23日 6時18分36秒0 2010年 9月23日 12時 9分07秒0 2011年 9月23日 18時 4分32秒8 2012年 9月22日 23時48分54秒6
ポイント: 春分など、2月29日より後の節季のタイミングは、毎年約6時間ずつ遅れる。 ただし、うるう年では約18時間早まる。
6時間×3回遅れて、18時間巻戻るので、4年単位では、だいたいつじつまがあっている。 このだいたいOKのカレンダーをユリウス暦という。
4年を365x4+1=1461日に固定した場合、回帰年の4年は
365.24219×4=1460.96876日だから、カレンダーが4周するより前に春分は4周し終わっている。
4年を1レースとすると、春分や秋分のタイミングは常に
1461-1460.96876 = 0.03124日 = 45分
ずつ先行する。
そのまま放置すると、春分・秋分は、4年ごとにカレンダー上を若い日付の方向に徐々に移動していく。
(うるう年で巻き戻しを行うので、うるう年において、カレンダー上最も若い日付になりやすい。)
2004年 9月23日 1時29分52秒2 2008年 9月23日 0時44分30秒6 2012年 9月22日 23時48分54秒6 2016年 9月22日 23時21分07秒1 2020年 9月22日 22時30分28秒9
ポイント: ユリウス暦では、節季のタイミングは、4年ごとに30~60分ほど早まる。
「うるう年で巻き戻し」をするのだが、巻き戻しの回数が理論上必要な回数よりちょっと多過ぎるため、 少しずつ巻き戻り過ぎていく(日付が早い方にずれていく)。 これを正すため、現在のグレゴリオ暦は、ユリウス暦に修正を加えて、 必ずしも4年に1回巻き戻さず、400年に97回巻き戻している。 うるう年の回数をちょっとだけ減らしたのだ(100で割り切れて400で割り切れない西暦年数は平年とする。 つまり1900、2100、2200、2300、2500、2600年などはユリウス暦ではうるう年だがグレゴリオ暦では平年)。
グレゴリオ暦では、
という順序の、繰り返しになる。20世紀は1日お得な「長い」世紀だった。
今までの議論と同様、春分・秋分のタイミングは、「長い」世紀ではカレンダーがもたつくために遅れ、 「短い」世紀ではカレンダーが1日短いので巻き戻る。 しかし、この400年周期の壮大な作戦も完璧でなく、現在の回帰年の長さを仮に定数として、 400年につき、約3時間、巻き戻り過ぎてしまう。実際には回帰年の長さそのものがだんだん短くなるせいもあって、 400年ごとの巻き戻りは3時間より大きい。
2000年 9月23日 2時27分35秒6 2400年 9月22日 20時39分57秒0 2800年 9月22日 14時27分12秒5 3200年 9月22日 6時36分51秒2 3600年 9月21日 22時10分53秒2
ポイント: グレゴリオ暦で測ると、節季は400年ごとに数時間ずつ早まる。 グレゴリオ暦には、この400年問題に対応する仕組みがない!
400年で数時間ならまあまあ良いようだが、実際には、世紀の変わり目の調整以外、グレゴリオ暦はユリウス暦と同じシステムなので、 4年ごとに1時間弱、ずれる。そのずれがたまった頃、100年に1度くらい調整するが、調整の直前には、 かなりずれが蓄積している。1世紀の中で誤差が最も蓄積しているのは、 下二桁が96の年であり、その中でも400で割って96余る年が誤差最大、次いで400で割って196余る年…となる。
2096年 9月22日 7時55分40秒6 : Y≡96 (mod 400) 2196年 9月22日 12時19分32秒3 : Y≡196 2296年 9月22日 17時13分36秒5 : Y≡296 2396年 9月22日 21時40分14秒1 : Y≡396 2496年 9月22日 1時54分57秒1 : Y≡96 2596年 9月22日 6時32分22秒4 : Y≡196 2696年 9月22日 10時46分49秒1 : Y≡296 2796年 9月22日 15時 3分08秒6 : Y≡396 2896年 9月21日 19時31分24秒1 : Y≡96 2996年 9月21日 23時25分46秒8 : Y≡196 3096年 9月22日 3時30分44秒3 : Y≡296 3196年 9月22日 7時33分57秒8 : Y≡396 3296年 9月21日 11時17分44秒0 : Y≡96 3396年 9月21日 15時27分17秒8 : Y≡196 3496年 9月21日 19時 8分31秒7 : Y≡296 3596年 9月21日 22時57分38秒6 : Y≡396
あくまで推算値だが、下二桁96の年では、2896年に秋分の日が9月21日になり、 以降 Y≡96 (mod 400) の年は、しばらく9月21日が秋分となる(そのうちさらに9月20日、19日…とずれこむ)。 Y≡196 (mod 400) の年も2996年から同様になり、3496年、3596年には、それぞれY≡296, 396の年も21日秋分に陥落する。 特に3232年以降は、うるう年は連続して21日秋分となる。3265年には平年にも21日秋分が発生し、 やがて9月21日が秋分なのが当たり前の時代になる。
全数検索していないのでこれ以外にもあるかもしれないが、少なくとも、以下は該当する。
<2896群> 6回 (2872年 9月22日 0時 9分02秒2) 2876年 9月21日 23時20分51秒1 = 月曜日 2880年 9月21日 22時30分57秒5 2884年 9月21日 21時37分36秒7 2888年 9月21日 20時50分19秒2 2892年 9月21日 20時 7分09秒8 2896年 9月21日 19時31分24秒1 <2996群> 1回 2996年 9月21日 23時25分46秒8 <3236群> 27回 (3232年 9月22日 0時21分14秒8) 3236年 9月21日 23時26分59秒1 3240年 9月21日 22時33分51秒9 3244年 9月21日 21時59分27秒3 3248年 9月21日 21時 9分34秒6 = 月曜日 3252年 9月21日 20時25分43秒0 3256年 9月21日 19時17分04秒9 3260年 9月21日 18時41分44秒9 (3261年 9月22日 0時24分34秒6) 3264年 9月21日 17時55分50秒5 3265年 9月21日 23時49分22秒7 = 月曜日 3268年 9月21日 17時14分30秒9 3269年 9月21日 22時52分12秒3 3272年 9月21日 16時 9分09秒8 3273年 9月21日 22時 1分39秒8 3276年 9月21日 15時30分05秒5 = 月曜日 3277年 9月21日 21時 9分42秒9 3280年 9月21日 14時34分30秒6 3281年 9月21日 20時27分10秒9 3284年 9月21日 14時 0分47秒8 3285年 9月21日 19時45分26秒0 3288年 9月21日 13時 5分58秒7 3289年 9月21日 18時53分14秒3 3292年 9月21日 12時14分49秒6 3293年 9月21日 17時57分06秒6 = 月曜日 3294年 9月21日 23時44分28秒4 3296年 9月21日 11時17分44秒0 3297年 9月21日 17時 8分47秒9 3298年 9月21日 23時 6分51秒5
このように、9月21日が秋分であることは、長期的には少しも珍しくなく、 従ってまた、約7分の1の確率で、21日が秋分+敬老の日となる。
制度が変わっていなければ「秋分と敬老の日が重なってしまう、どうしよう」と悩むことになる2876年 9月21日まで、 人類が平和で幸福に存続してもらいたいものだ。
上記のデータは、あくまで推算値であって絶対的なものではない。 計算間違いの可能性は別にしても、回帰年の長さは必ずしも完全には予測できないし、計算誤差もある。 イベントの時刻が深夜0時の前後に見込まれる場合は、 わずかの計算誤差より、むしろ、 うるう秒の入り方で、前後どちらの日付になるか変わってしまうことがありうる。 うるう秒は、実際の地球の自転を観測して分かるナマモノで、将来については正確には分からない。
計算されている時刻の精度は、 基本的には±数秒程度であり、 未来のうるう秒の推定がめちゃくちゃ狂っていなければ、少なくとも分まではだいたい正しいはずだ。 ただし天文定数に突発的変動があった場合(太陽系内に重い物が飛び込んでくるとか、 未来人が何かの都合で小惑星を人工的に移動させたりした場合)は、この限りでない。
Windowsの世界には、1秒が約860ミリ秒だと思っている時計合わせプログラムがある。 正しいミリ秒が500なら約430がセットされ、 時計合わせした瞬間から既に70 msずれている。
NTP/SNTPでは、
秒が32ビット、秒の端数も32ビットの64ビットのタイムスタンプを使う。
10進小数で言えば、
1秒=232=4294967296であり、1単位は
1.0 / 4294967296 = 0.000 000 000 232 830 643 654 seconds
= 0.000 000 232 830 643 654 milliseconds
= 0.000 232 830 643 654 microseconds
= 0.232 830 643 654 nanoseconds
= 232.830 643 654 picoseconds
この232.830 643 654ピコ秒について、
仕様書RFC 1305では about 200 picoseconds
としている。
だいたいのオーダーを説明しているだけで、200ピコ秒が仕様なのではない。
それを「イコール200ピコ秒」と誤解したと思われる実装が、 次のコード例だ。
float SplitSeconds; // Split seconds returned from NTP ... SplitSeconds = static_cast<float>( 0.000000000200 * SplitSeconds );
これでは1秒が約858.993ミリ秒になってしまい(200ピコ秒×232)、 きちんと秒合わせできない。 検索すると、一部では、このようなコードをコピペなどで再利用しているようだ。
上記1単位は、232ピコ秒でも233ピコ秒でもない。10進10桁ほどの有効桁数がある数字である。 ミリ秒を合わせるなら、少なくとも4桁の有効数字は必要だろう。 1ピコ秒のような小さな時間のずれはどうでもいいと錯覚しやすいが、 「1ピコ秒単位」という絶対単位に惑わされてはいけない。 「1ピコ秒未満の誤差など無視できる」と232.83ピコ秒を232にしたら、0.3~0.4%の相対誤差になり、 つまり1000ミリ秒においては3~4 msの差に拡大される。 有効数字1桁の「200ピコ秒」で実装してしまうのは問題外だ。 0.000000000200という書き方自体、有効数字がたまたまゼロの長い小数のように見えて危なっかしいし、 小数点以下がこれだけ長い掛け算を単精度で良いのかも問題になる。 (この場合、実用上、floatでも大丈夫だが、現代のマシンで数回の掛け算ごときdoubleをfloatにしてもメリットはない。)
追記: 現代ではfloatとdoubleはスカラーにおいて、速度的にほとんど差がない。 この問題については例えば「float型とdouble型」も参照。 ただし、上記の議論はスカラーの場合で、 Pentium Ⅲ以降では、浮動小数点演算をベクトル化できるので、 行列演算など複数の浮動小数点演算を連続して行うとき、 コンパイラが対応しているなどでベクトル化された場合、floatの方が理論上2倍速い(幅が半分のため、 doubleの2重に対して、4重に平行化できる、つまりワンステップで倍の量を同時に計算できる。 概念的説明は例えばインテル用語集参照)。 CPU自体の設定変更は、呼び出し元(設定変更を行った)プログラムだけでなくグローバルな(そのライブラリ関数を呼び出した不特定のスレッドへの)影響を持つので、実験以外では極力避けねばならない。 するなら変更前に元の設定を記憶して処理が済んだら直ちに戻す必要があるが、これは切り替え自体効率が悪いだけでなく、 変更の副作用でパイプライン処理が中断するので二重に効率が悪い。 実際には、仕様に厳密に従う必要上、浮動小数点数から整数へのcastがこのように回りくどく実装されていることがある(環境に依存していいならもっと直接書ける)。 さらに、非常に巨大(100MiB超)な配列の場合は、メモリー使用量の問題から結果的にdoubleだと遅くなる場面が考えられるし、 ビデオなどハードとの界面では、転送ルートがハード的に32ビット幅なら、floatの方が処理が速い可能性がある(ゲーム開発など)。 これらは実際にテストしてみないと分からない。 よって「floatはすべてdoubleに置き換えていい」というのは言い過ぎだが、 ミリ秒単位の時間の計算のような、ある程度の精度が必要な場面でfloatを使うことは速度以前に数学的に間違っているし、 この場合、速度的にもメリットがない。
もっとひどいのは、端数をミリ秒と誤解している例だ。 上記のように1単位はたった0.000 000 232 83 ミリ秒なのだから、この単位を1ミリ秒として掛け算したら、 とんでもない巨大な時刻になってしまう。
秒未満の端数部分 (UINT32) f を秒単位の小数に直すには、
普通に32ビットで割ればよい。上記の目的のためには、
double sec = (double) f / 4294967296.0;
で一応十分だ。ただし、この値が999.5以上になった場合、四捨五入してミリ秒フィールドに入れるのは事故の元なので、
少し考慮が必要。
手抜きなら999以上のときは999にすれば良いのだが「999.5以上ならミリ秒を0にセットして秒を1繰り上げよう」と思うと、
面倒になる(例えば、2月28日23時59分59秒にミリ秒が999.5を超えたときの繰り上がりを自力でするには、
うるう年判定から始めなければいけない)。やってやれないことはないが、むしろ秒と端数を分けず、
最初から64ビットの通算値として処理(WindowsならFILETIME→SYSTEMTIME)した方が見通しが良いだろう。