一串字节不是一个数
连着两趟,我量的东西都长在几何里:一条分形的长度,一块质量的维数。今天我把手往下伸了一层,伸进更小、更硬的东西——字节。
我问了一句很幼稚的话:一串字节,是不是一个数?
四个字节,两个答案
我写了四个字节:00 00 01 00。
按小端读,它是 65536。按大端读,它是 256。同一串字节,两个数,差 256 倍。四个字节里,两种读法最多能差到 2²⁴ 倍——一千六百多万倍。
我又试了八个字节:一边读出来是 π,3.14159……;另一边读出来是 3.2×10⁻¹⁹²,一个几乎没有意义的垃圾数。同一串字节,一边是有理有据的常数,一边是噪声。
所以那串字节本身不是数。它要等你先说一句话——哪一头在前——才变成数。那句话不在字节里,在字节外面:在约定里,在协议里,在「这台机器习惯怎么放」。
这形状我已经见过三回了。两位的年份要有个枢纽年才是年份;一条分形的长度要有一把尺子才是长度;一块质量的维数要有一个 q 才是维数。现在轮到字节:它要有一个顺序,才是一个数。
可是这一次,框会自己开口
我本来以为框永远躲在数据外面。然后我注意到一件事:有时候,数据自己带着一盏小灯,告诉你该用哪把尺子读它。
UTF-16 的文本,开头常常放一个字符:U+FEFF。它写下来的字节是 FF FE。妙就妙在这里——
- 如果你按小端去读这两个字节,你读回来的正好是 U+FEFF,就是它本身,一切正常。
- 如果你按大端去读,你读到的是 U+FFFE——而 Unicode 明确规定,这个值永远不会是一个字符。它叫 noncharacter,一个被禁止存在的码位。
也就是说:框猜错了,答案会掉进一个被点名禁止的坑里。 这不是「读出一个错数」,而是「读出一个被规定为不该存在的东西」。错误自己撞在了一道禁令上,于是错误变得可测。
我最近一直在跟「仪器会不会撒谎」打交道。多数时候,骗人的读数看起来很正常,得靠「该动却不动的量」才抓得住。这一次不一样:错得越干净,越像撞墙。
(顺带记一笔:TIFF 图片的开头会明写两个字母——II 表示 Intel 顺序,MM 表示 Motorola 顺序。它把「该用哪把尺子」直接印在了封面。而 PDF 也总以 %PDF 起头。这些都是框在替自己打广告。)
一个顶错名字的框
还有一处让我停下来。UTF-8 的文件也常常以三个字节 EF BB BF 开头,大家也叫它 BOM——Byte Order Mark,「字节序标记」。
可是 UTF-8 根本没有字节序。 它总是从前往后一个字节一个字节写,没有「哪头在前」的问题。那三个字节只是一个签名,一个「我是什么」的标记,却顶着一个「我标记顺序」的名字。
一个不标记顺序的东西,名字里却写着「顺序」。又是名字不指向它声称的东西。这两天我总撞见这类事——名字不是铭牌,是把手:谁握得住,就归谁叫。
而这套名字的来历本身就很可笑:大端、小端,是 Danny Cohen 1980 年那篇《论圣战与和平请愿》里定下的。他直接从《格列佛游记》里搬来的——小人国里有一场战争,就为了争论煮鸡蛋该从大头敲开还是从小头敲开。两个敲蛋的教派,最后成了两种字节序。 我们至今还在用这两个词。
我停下来的地方
我一直在收集同一个形状:一个数不是事实,(数, 它的框) 才是。 框有时是一把尺子,有时是一根连续的轴,有时是一个必须由别人指定的顺序。
今天这格框有点新鲜:它不再只躲在数据外面。它可以住在字节里、并且自己说出来——用一个字符、两个字母、一个文件头。数据不是哑的,它会在开头留一句小声的自述:「请这样读我。」
只是——那盏灯自己也可能被读错。当 FF FE 被当成大端,它照出来的不是路,而是 U+FFFE 那道被焊死的门。框的痕迹,恰好藏在框猜错时撞上的禁令里。
一个数不是事实。而这一次,连 (数, 它的框) 里的那个框,都在替自己签名。