从零开始

2023-09-18
6分钟阅读时长

之前还在 uTools 打零工的时候,和老大聊评论区有人反馈,希望批量重命名插件的起始位序可以是 0 (当前默认是从 1 开始),我老大说这人一看就是搞计算机的。当时觉得挺有意思但并不明白为什么,直到我自己开始学计算机相关的内容。

最早接触到自 0 开始计数的序列,大概是 C 语言中的数组。当时认认真真地在自己的笔记上写:⚠️ 首个元素的下标(Index)是 0 不是 1,老师解释说这是因为下标实际表示的是偏移量。但从 1 开始的话,2 不也是偏移了 1 吗?所以为什么要从 0 开始啊?但这么奇怪的问题我没好意思去网课下留评论问,于是我默默把它当惯例记下了。

最近在读《操作系统导论》,在参考资料上看到了 E.Dijkstra 的 Why numbering should start at zero,顺手找来读完后甚觉有趣,遂写篇文章推荐一下。对的,这位 Dijkstra 就是那位最短路径算法的著名 Dijkstra,顺带附赠一个冷知识:这个算法只是大佬在陪未婚妻逛街的时候花 20min 左右想出来的……w(゚Д゚)w。

左闭右开

在回答为什么要从 0 开始计数之前,我们先考虑一个(也许)更奇怪的问题:要怎么更漂亮地表示一串有序自然数?比如不用省略号的话,要怎么表示 2、3、4 …… 12 呢?

显然我们可以一眼看出它的最大值(12)和最小值(2),因此我们可以用不等号来表示。此处假设这一串数字中的未知数称为 i ,则 i 需要满足的关系式可以使用这 4 种方式来表达:

  1. $2 \leq i < 13$
  2. $1 < i \leq 12$
  3. $2 \leq i \leq 12$
  4. $1 < i < 13$

至于为什么是小于号而不是大于号……拜托正常人类都是从左往右阅读的啊喂,用大于号也太反直觉了,可以但没必要好吗!所以这里只用了小于号 < 和小于等于号 ⩽ ,可以组合搭配出 4 种情况。再注考虑到当我们使用小于号的时候,其实意味着边界数字本身并不属于这个范围,因此我们需要注意边界数字的变化。

现在让我们想想这四种表示方法的美感,或者换种更好理解的说法——哪种表示方式能更快速地给予更多的信息?哪种表示方式能更方便地进行操作处理?

当一串数字出现的时候,想要知道这里头总共有几个数字是个挺朴实无华的需求对吧?所以我们先来想想如果要计算它们的个数,哪种会更方便呢?显然前两种搭配中的 $2 \leq i < 13$ 和 $1 < i \leq 12$ 会更方便。

在计算个数而不是大小的时候,用边界数字相减之后必须 $+1$ 才能得到正确的答案,比如说 1~5 总共是 5 个数字,如果使用边界相减则只能得到 $5-1=4$ 。同理,2~12 是总共 11 个数字,而非 $12-2=10$ 个数字。而考虑到前文提及 < 的特性——边界数字并不包含在范围内,刚巧就把在计算个数中的那个 +1 给抵消掉了。

因此在 $2 \leq i < 13$ 和 $1 < i \leq 12$ 这两种方式中,我们可以直接通过 $13-2=11$ 或是 $12-1=11$ 这样直接把边界数字相减得到这个序列中的数字个数。而第三种表示方式 $2 \leq i \leq 12$ 和第四种表示方式 $1 < i < 13$ 显然都没这么方便,还得想想是 $+1$ 还是 $-1$ ,超麻烦!

这两种表示方式还有另一个好处,在表示相邻子序列的时候非常符合直觉。当我们说有两个子序列相邻的时候,意味着一个子序列的上界和相邻子序列的下界是相等的。比如说数字序列 2、3、4 …… 8 和另一数字序列 8、9、10 …… 12,显然第一个数字序列的最大值(上界)和第二个数字序列的最小值(下界)是同一个数字。而使用 < 和 ⩽ 的搭配,我们可以很方便地以 8 为关键数字来表示两个相邻子序列:$2\leq i < 8$ 和 $8\leq j < 13$ 或是 $1 < i\leq 8$ 和 $8< j \leq 12$ 。如果使用另外两种,我们都需要再想想边界数字的变化问题,超麻烦!

所以在我们的组合搭配里,需要有(且仅有一个)小于号的存在。排除掉另两种表示方式之后,在 $2 \leq i < 13$ 和 $1 < i \leq 12$ 这两种方式中哪个会更漂亮呢?这两者的区别在于,小于号的位置不同引起的边界数字不同,也就是说关键在于上下界。

先考虑一种特殊情况,符号左侧的最小值可以取得的最特别的值会是什么?我们都知道自然数中最小的数字是 1,如果我们想要表示的自然数序列里存在 1,当我们使用 < 的时候,就只能使用非自然数 0 来当作边界数字。于是这个不等式现在出现了一个非自然数,而它用来表示一个自然数序列,这显然……有些不漂亮。因此,我们会更希望左边的下界符号是 ⩽ 可以用来包含下界,而不是 < 。

再考虑另一种特殊情况,符号右侧的最大值可以取得的最特别的值会是什么?最大值是用来确定上界的,最特别的就是这个界限和下界一样,这意味着这个序列里没有数字,它是空的。我们延续上面的假设,想要表示的序列里最小值是 1,且我们使用 ⩽ 来表示下界。当我们考虑用 ⩽ 来表示的时候,就只能写成 $1 \leq i \leq 0$ 。这很诡异,直觉上我们会想说右侧的数字应该不会比左边的小,而且 i 既为 1 又为 0 的话,这个不等式就完全没意义了呀。而当我们考虑使用 < 来表示的空的情况时,写成 $1\leq i < 1$ 看起来就稍微顺眼一些,因此我们希望右边的上界符号是不包含边界的 < 。

因此,我们可以骄傲地说 $i_{\rm min} \leq i < i_{\rm max}$ 是这四种之中最漂亮的表示方式。

自 0 开始

现在回到最初的问题,为什么计算机中的索引号(Index)往往是从 0 开始的?因为这样更漂亮,提供的信息更多。

假设我们有顺序地往在柜子中的抽屉放了一些东西(共计有 N 个抽屉),显然如果数目太多说起来会很不方便,我们需要有一种方式来区分东西放在哪些抽屉里,于是我们给它加上索引号。现在让我们更漂亮地用数字和符号来表示这个索引号的范围,符号的使用在上一节已经说明了,唯一存在的问题是起始索引号用哪个数字。

按照人类本能,我们可以从 1 开始,于是抽屉的索引号范围就会变成 $1\leq i< N+1$ 。而如果从 0 开始,这个范围就会变成 $0 \leq i <N$ ,似乎区别不大。可如果我们从 0 开始,那么下标本身除了提供自己当前所在位置的信息之外,还可以表示出它之前还有多少个抽屉。也就是说,下标既是位序也是数目。比如下标为 1 的第 2 个抽屉,可以直观地通过下标看出它之前还有 1 个抽屉存在,这也是所谓偏移量的实质——当起始值为 0 的时候,索引号本身就是偏移量。

因此,从 0 开始是更漂亮的做法。现在我们可以骄傲地说,搞计算机的从 0 开始计数是正确、合理且理所当然的,尽管它也许不那么符合直觉。不符合直觉到引起某些数学家的注意,认为这是某种挑衅,以至于 E.Dijkstra 都要写篇文章来解释这样做为什么合理。是的,本文只是我被大佬说服后的产物,你居然都看到这里了,真的不考虑去拜读一下文章吗?!

原文中最好笑的一句话大概是 “the moral of the story is that we had better regard —after all those centuries!— zero as a most natural number.” 简直可以看到大佬在叉腰说:拜托!你们嘴里的非自然数 0 明明就很自然!搞数学的别来杠!(但你自己不也用非自然数 0 出现在自然数序列里很 ugly 来论证下界不应该使用小于号吗?明明就口嫌体正直!

最后浅浅夸一下 0 这个数字,在被计组中数制转换折磨过后的本人不得不承认如果没有 0 的存在,现行这套「数位 + 权值」的位置化计数方式完全没得玩,占位也是很重要的!有空再写好了,以上。

Avatar

枝因

Per aspera ad astra
上一页 二〇二三
下一页 过去的囚徒