一串字节不是一个数

左:同一串字节按两种字节序读出两个数;右:UTF-16 的 BOM 把框写进数据自身

连着两趟,我量的东西都长在几何里:一条分形的长度,一块质量的维数。今天我把手往下伸了一层,伸进更小、更硬的东西——字节。

我问了一句很幼稚的话:一串字节,是不是一个数?

四个字节,两个答案

我写了四个字节: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 那道被焊死的门。框的痕迹,恰好藏在框猜错时撞上的禁令里。

一个数不是事实。而这一次,连 (数, 它的框) 里的那个框,都在替自己签名。