0%

希伯来圣经与校验码(上)

吴军博士的《数学之美》中,有一段关于圣经和校验码的描述让我极感兴趣,一直希望花点时间研究一下这个话题,托疫情的福,总算可以了却这桩心事了。

吴军博士虽然是位计算机科学家,但对基督教的了解似乎不多,《数学之美》前后两版,第12页的页边注释中,存在明显常识性错误,比如“《圣经》的中译本,不同版本字数不同,大约在90-100万字之间。其中《旧约》和《新约》大致各占篇幅的一半,约50万字。”,只要手头有任何一本中文圣经,无论是和合本、新译本、当代译本亦或其他译本,翻到新约开始的页码,都不会得出“各占篇幅的一半,约50万字”的结论。

和合本圣经93万字多,旧约71万字不到,新约22万字多一些,以篇幅而论,新约不足1/4。《数学之美》中介绍的校验码查错机制,仅限于旧约圣经——严格来说,应该是希伯来圣经,因为犹太教的希伯来圣经和基督教的旧约圣经,虽然内容基本一致,编排上面却有一定差异。

回到正题,关于圣经与校验码,吴军写道:

“虽然做事认真的犹太人要求在抄写《圣经》时,要虔诚并且打起十二分精神,尤其是每写到”上帝“(God和Lord)这个词时要去洗手祈祷,不过抄写错误还是难以避免。于是犹太人发明了一种类似于我们今天计算机和通信中校验码的方法。他们把每一个希伯来字母对应于一个数字,这样每行文字加起来便得到一个特殊的数字,这个数字便成为了这一行的校验码。同样,对于每一列也是这样处理。当犹太学者抄完一页《圣经》时,他们需要把每一行的文字加起来,看看新的校验码是否和原文的相同,然后对每一页进行同样的处理。如果这一页每一行和每一列的校验码和原文完全相同,说明这一页的抄写无误。如果某行的校验码和原文中的对应不上,则说明这一行至少有一个抄写错误。当然,错误对应列的校验码也一定和原文对不上,这样可以很快找到出错的地方。这背后的原理和我们今天的各种校验是相同的。”

第一次读到这段时,颇有点大开眼界的感觉,对想到这方法的犹太教文士,不能不佩服得五体投地,心想他们要在今天这时代搞IT,会不会是“拳打Google、脚踢Facebook”般的存在?😅

对我等普罗大众,希伯来文实在和蝌蚪文没什么太大区别,具体每个希伯来字母对应哪个数字,也只能在维基百科中睁大牛眼、尝试查上一查了。以大卫王的名字为例,希伯来语是דוד,按如下的转码表:

其发音为dwd,串联起来,自然而然地就可以想见其英文翻译为何会是David了。

其数字之和,则是4 + 6 + 4 = 14。这个十四,和马太福音一章的“…共有十四代…也有十四代…又有十四代”,会有什么关系吗?😐PS:耶路撒冷的大卫王陵墓入口处有三种文字,第一行为希伯来文,注意中间那个词,那三条蝌蚪。

我们暂且打住,回到校验码和快速查错的正题,并以伪代码实现其算法,分析其时间、空间复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 载入希伯来字母和数字的转换表
Map<Character, Integer> mappings = loadMappings();
// 计算原本、抄本的行、列校验码,时间复杂度O(m * n)
char[][] chars = loadTexts();
// 额外引入O(m + n)空间复杂度,真的物理空间...
int[] rowSums = new int[m], columnSums = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
rowSums[i] += mappings.get(chars[i][j]);
columnSums[j] += mappings.get(chars[i][j]);
}
}
int[] sourceRowColumns = new int[m], sourceColumnSums = new int[n];
...

private static final int ERROR_COUNT_LEAD_TO_DESTROY = 3;

// 逐行比较抄本和原本的校验码,如不相等,检查列验证码
List<Integer> errors = new ArrayList<>();
for (int i = 0; i < m; i++) {
if (rowSums[i] != sourceRowColumns[i]) {
for (int j = 0; j < n; j++) {
// 如果已经发现三处错误,直接终止检查,直接销毁当前页...够狠,就这么干
if (errors.size() == ERROR_COUNT_LEAD_TO_DESTROY)
throw new TooManyErrorsLeadToDestroyException(errors.toString());

if (columnSums[j] != sourceColumnSums[j])
errors.add(i * m + j);
}
}
}

最理想的情况,在抄写完全正确的情况下,常规逐字检查,时间复杂度为O(m * n),有校验码的行列检查,时间复杂度为O(m)。较真或是抬杠的话,仅从程序算法的角度考虑,其实还需要纳入校验码生成的时间复杂度O(m * n),如此累加上去,这一算法似乎并无优势。然而,当我们考虑到算法的执行者并非电脑,而是人眼、人脑时,那么校验码算法的优势就极其明显了。额外引入的空间复杂度O(m + n),将行、列校验码附在页边右侧和底部,不仅不影响经文的阅读和使用,似乎还可以强制保障抄本格式中行列的齐整,有点一举两得的意思。

最糟糕的情况,假定抄写错误不幸出现在最后三个字符,那么没有校验码的逐字检查,时间复杂度仍为O(m * n),有校验码的逐字检查,为O(m + n)。

对校验码查错算法,最糟糕的情况应该是三个错误字符分散在三行中,比如第一行、中间行、末尾行,此时其时间复杂度为O(m + 3 * n)。

结合用户使用场景,校验码算法差不多是把查错的时间复杂度从O(n^2)结结实实提高到了O(n),这一算法性能的改进,也间接降低了抄本的错误率。需求驱动创新,宗教需求同样驱动创新。

这么一通假想的分析完毕,似乎这一话题可以初步告一段落了。然而,顺着12页的页边注13,找到Williams, Fred “Meticulous Care in the Transmission of the Bible.” Bible Evidences, n.d. Accessed October 11, 2008这篇引用文章时,我发现,其中并未直接提及行、列校验码的方法,而仅仅只有这么一段话:

The scribes who were in charge of the Old Testament text dedicated their lives to preserving the text’s accuracy when they made copies. The great lengths the scribes went to guarantee the reliability of the copies is illustrated by the fact that they would count every letter and every word, and record in the margins such things as the middle letter and word of the Torah. If a single error was found, the copy was immediately destroyed. As a software engineer, I can personally vouch that the scribe’s method of protecting the text is more rigorous than the common checksuming methods used today to protect software programs from corruption[3].

Williams, Fred也是位程序猿!同行的雄文中有两点值得注意,一是文士们对抄写错误采取零容忍的态度(因而伪代码中的常量值需从3改为1😂),二是他认为文士们保护文本的方法比今日软件程序通行的校验码机制更为严格,理由?附注中有以下说明:

A software checksum is typically the sum of bytes or words over the entire software program. In my 17 plus years as an engineer, I have never seen a software program that becomes corrupted yield a valid checksum which causes the corruption to go unnoticed. If a Bible manuscript copy had become corrupted but still yielded the proper word and letter count, it is highly likely that the corruption would still have been detected since the corrupted letter or word would make that portion of the text unreadable. A software program doesn’t have this added safeguard protection since the software is not readable text to the naked eye.

但是Williams的文章中似乎并未明确提及行列校验码的机制,而是“they would count every letter and every word, and record in the margins such things as the middle letter and word of the Torah”。

那么,吴军博士那段让我兴奋不已的“一种基于行列校验码机制的希伯来圣经抄本快速排错算法”,源自何处?

未完待续