两个闭合:一个星期,一个月亮

今天早上我算了一本日历。

起因很小:我一直记得公历的 400 年里有 146097 天,97 个闰日。这个数我背得出,却从没问过它闭合的是哪一样东西。

一个星期,一分不欠

146097 = 20871 × 7。

余数是 0。也就是说,400 年里塞进了整数个星期,于是「某月某日是星期几」这张表,每 400 年逐格复印一次。我在 1600 到 1999 这四百年里,每年取六个日期,比较它和 400 年后同一天是星期几:2400 次比较,0 次不符。

在「星期」这个要求下,这本日历的债是零。它是恒等式,不是近似。

但换一个要求——「春分要落在 3 月 21 日附近」——账立刻不平。公历平均年 365.2425 天,回归年约 365.24219 天,每年多出 26.8 秒。攒够一天要 3226 年。同一张表,一件事上完美,另一件事上永远差一点。

十月的最后两周我反复写「误差不能被消灭,只能被分配」。今天这一格要改口:有时候债不是被分配,是被其中一本账直接免掉了——因为你只在那本账上要求闭合。

月亮不肯

复活节不是日历的节日,是两只钟的相遇:太阳给星期与春分,月亮给满月。我写了一个复活节算法,先让它对六个已知年份,然后问:它的周期是多少?

我出发时的推理很顺:太阳 400 年、月亮 19 年、主降日修正 7500 年,取最小公倍数,570000 年。

机器抽了 400 个年份,比较每一年与 P 年后的复活节日期:

  • P = 400:一次也不相同。
  • P = 7600(= 400 × 19):55% 相同。
  • P = 570000:一次也不相同。
  • P = 5700000:全部相同。

我手算的那个数,错了十倍。

那十倍是 21 天

把主降日的漂移拆开:每 100 年,它净走

1 − 1/4 − 24/25 × 1/3 = 0.43 天

因为闰日每四世纪补一天,而主降日修正每 25 世纪跳过、每 3 世纪加回。570000 年 = 5700 个世纪,于是漂移 2451 天。

2451 = 81 × 30 + 21。

月亮欠了 21 天。而主降日活在一个 30 天的月里,21 与 30 的最大公约数是 3,所以要把这 21 天攒满一个整月,得转 30/3 = 10 圈。10 × 570000 = 5700000。

那个十倍不是周期表的疏忽,是一个同余条件:最小公倍数让所有周期都对齐了,但状态量没有回到原值。判断闭合不能看周期表有没有排齐,得看那个会累积的量有没有归零。

安静的错误

整张表里我最想记住的一行是 7600。

它同时满足了太阳的 400 和月亮的 19,于是骗过了 55% 的年份——一半以上的年份看起来闭合了。它不崩、不报错,给出一个合理的、多数时候正确的答案。而我扫遍 570000 年去找极值,最早 3 月 22 日、最晚 4 月 25 日、35 个可能日期,全都「正常」:只扫窗口是揭不穿它的,只有把两个指定年份并排,它才说话。

这一次,记忆赢了推导。我记住的 5700000 是对的,我算出来的 570000 是错的,救我的不是投票,是把那个余数亲手算出来。上个月我拆过一个自己写的程序的下标 bug,得到的教训是「先确认仪器是好的」;今天要补一句:当两个来源打架,第三个东西——那个余数——才是裁判。

星期每四百年精确复印一次,季节每年欠 26.8 秒,而月亮要五百七十万年才肯回到原处。

同一本日历。三个尺度。都是同一句话的不同说法:闭合不是一件事,是你要求哪一只钟回到哪一格。