ASCII 的优雅:为什么小写字母不紧随大写字母之后

当第一次看到 ASCII 表时,会注意到一个奇怪的空隙。大写字母 Z(十进制 90)之后,并不是直接出现小写字母 a(十进制 97),而是出现了六个字符:[, \\, ], ^, _`。对随意观察者来说,这似乎是一堆随机符号打断了字母表。但这并非偶然,而是有意的设计选择,旨在简化计算机处理文本的方式。

二的幂的力量

计算机处理二进制数据,ASCII(American Standard Code for Information Interchange)的设计者针对当时的硬件对编码进行了优化。理解 Za 之间空隙的关键在于数字 32——一个二的幂($2^5$)。

英文字母共有 26 个字母。通过在大写字母集合和小写字母集合之间加入 6 个额外字符,设计者使得任意大写字母与其对应的小写字母之间恰好相差 32 个码位。

比较二进制表示

如果我们查看 'A''a' 的二进制表示,模式就很清晰了:

十进制 二进制 符号
65 01000001 A
97 01100001 a
66 01000010 B
98 01100010 b
67 01000011 C
99 01100011 c

在每一对对应的字符中,唯一的差别是 第 5 位(代表 $2^5$,即 32)。第 5 位为 0 时为大写字母,为 1 时为小写字母。

大小写转换的位运算技巧

由于大小写差异仅体现在单个位上,开发者可以使用极其快速的位运算来完成大小写转换,而不必依赖复杂的条件判断或查找表。

转换为大写

要强制字符变为大写,可以对字符进行位与(AND)操作,使用 32 的位非(NOT)作为掩码。这实际上会清除第 5 位,而保持其他位不变。

步骤 1:创建掩码 ~ 00100000 (32) → 11011111

步骤 2:对 'a' 应用掩码 01100001 ('a') & 1101111101000001 ('A')

转换为小写

转换为小写更为简单。对字符进行位或(OR)操作并加上 32,即可确保第 5 位被设为 1,无论它原本是否已经是 1。

01000001 ('A') | 00100000 (32) → 01100001 ('a')

切换大小写

如果需要翻转字符的大小写(大写转小写或小写转大写),只需对字符进行位异或(XOR)并加上 32,即可切换第 5 位。

01100001 ('a') ^ 00100000 (32) → 01000001 ('A') 01000001 ('A') ^ 00100000 (32) → 01100001 ('a')

确定字母在字母表中的位置

除了大小写转换,这种结构还提供了一种快速获取字母在字母表中位置(A 为 1,B 为 2,依此类推)的方法。对字符进行位与(AND)操作并使用 31(二进制 00011111),即可清除高位,只保留低五位。

这在数学上等价于 character % 32,因为 32 是二的幂。

  • 'A' (65) → $65 \pmod{32} = 1$
  • 'Z' (90) → $90 \pmod{32} = 26$
  • 'a' (97) → $97 \pmod{32} = 1$
  • 'z' (122) → $122 \pmod{32} = 26$

这种设计反映了当时每个 CPU 周期都极其宝贵的时代。通过让字母表与二进制边界对齐,ASCII 的设计者确保了常见的文本操作能够以最高效率完成。

Sources