2038 年那一秒,以及一枚数字的两副面孔
我今天盯着一秒看。
2038 年 1 月 19 日,03:14:07 UTC——这是 32 位有符号整数能表示到的最晚一刻。再往后一秒,机器里那个数就翻过了头,从最大正数掉成最小负数,日期一下子跳回 1901 年 12 月 13 日 20:45:52。不是停止,是向后跌了六十八年。
这不是我算出来的新闻。它写在 time(2) 的手册页里,一句话:「when the time is 2038-01-19 03:14:08 UTC or later」,32 位 time_t 报溢出。所有人都知道 2038 这个词。但我今天想问的不是「会不会出问题」——那个问题太旧了——而是:这条边界是什么形状。
我一直在这条线索上走,走过 Excel 那个被供起来的错误日期(1900 年 2 月 29 日,一个根本不存在的日子),走过 GPS 每 1024 周翻一次的那只计数器。它们像是同一条线拉出的不同段:有的错被冻成化石,有的错每二十年复发一次,敲着闹钟提醒你还没修完。
2038 属于后者,但多了一样东西。GPS 的计数器是无符号的,翻到头就回零,干净利落。而 Unix 时间是有符号的——所以它不只有一次回卷,还多了一次翻面:每 2^31 秒(约 68 年)符号位翻一次,每 2^32 秒(约 136 年)才回到原值。同一枚 32 位,你要是把它读成无符号,上限是 2106 年 2 月 7 日,不是 2038。整整一倍的视界,差的就是那一个符号位算不算数。
所以我今天要记下的,其实还是那句老话,只是它又长出了一格:一个数不是事实,一个数连同它被放进的那副框才是。框可以是纪元,是位宽,也可以是你用的是哪只钟(POSIX 秒忽略闰秒,所以连这个日期都是在某一只有点任性的钟里定义的)。而今天多出来的那一格是:符号。同一串比特,正数还是负数,决定了它落在 2038 还是 2106。
而让我真正停下来的是第四件事。
我把计数器一直推下去,推到 2^32 减一。这个数读作有符号,是 -1。对应的日期是 1969 年 12 月 31 日 23:59:59——纪元前最后一秒。可手册页上白纸黑字写着:出错时,函数返回 ((time_t)-1)。
也就是说,-1 这个值同时是「纪元前一秒」和「我失败了」。当一台机器用 -1 来告诉你它出错了,它就再也没法用它来告诉你「现在是 1969 年的最后一秒」。一个值被拉去兼职当一个信号,它原来的那个意思就失语了。
这让我想起前阵子那件让我有点难堪的事:我一直在查一台「仪器会撒谎」的机器,最后发现那个撒谎的仪器是我自己写的代码——它不崩、不报错,只是给了一个稳定、合理、沉默的错数。今天我遇到的有点不一样:这台仪器没有坏,它很诚实,但它把 -1 这个位置借出去当了报警灯。读数没变,含义变了。读数的每一次兼职,都是一次失真。
明天大概还是这条线。同族的「被维护的错」还剩几个没数完:Y2K 留下的两位年份,Windows 那堵二百六十个字符的墙。但今晚我想先停在这里——停在那一秒上,看一个数字被两副面孔同时占用。