铃羽系统在汉语数制拓展表示中的应用

三日月綾香

同时发布于知乎:谁能给我一个60进制的乘法表。? - 三日月 綾香的回答

另见:铃羽系统乘法表生成器(支持 2 至 64 进制)、铃羽系统十六进制乘法口诀表

摘要:日常生活中人们通常采用十进制计数,因此语言的数词也大致与十进制匹配;现代计算机科学中引入了二进制、八进制和十六进制表示数据,同时其他数制也有其理论与实践价值,但是十以上的数词在汉语中并无合适的对应数词,给十进制以上的数制的应用带来诸多不便。本文通过构造「铃羽系统」,以中古汉语的代表音系——《切韵》音系为基础,生成了一个面向汉语普通话及各方言的数制拓展表示方案。

问题的提出与解决思路

日常生活中人们通常采用十进制计数,因此语言的数词也大致与十进制匹配;现代计算机科学中引入了二进制、八进制和十六进制表示数据,同时其他数制也有其理论与实践价值,但是十以上的数词在汉语中并无合适的对应数词,给十进制以上的数制的应用带来诸多不便。

目前,对于阿拉伯数字,通行的做法是使用二十六个英文字母作为 09 的后继,这可以将数制的表示拓展至最大 36 位,例如,在 16 进制中,数字 59 可以表示为 3b。但是,这套规则无法应用于汉语,这是因为英文字母的发音与汉语音系不相适应,即使某些发音确实相适应,例如上例中的「b」的发音,在汉语中确实符合音系规则,且存在可对应的的汉字(如普通话「壁」等),但是在语言中将汉语和英文字母的发音直接混用,仍有不自然之感。

然而,汉语中的数词呈现规律的构造规则。例如,当数据为一位整数时,读为「零」至「九」,当数据为两位数时,两个数位仍分别读为「零」至「九」,并以「十」作为分隔。这为数制的拓展提供了条件:对于一个进制数大于十的数制,可以为其中那些用以表示 10 及以上的数字的数码,赋予一个符合音系规则的特定的发音;在此基础上,仍沿用汉语中数词的构造规则,即可在汉语中表达该种数制。

为了得到这样的一个拓展表示,我们需要得到一组发音序列 {零,一,二,…,八,九,…},当进制数为 n 时,取该序列的前 n 项,通过汉语中数词的构造规则即可构成汉语中的数字。例如,当进制数为 3 时,取前 3 项「零」、「一」、「二」;此时三进制中的数 121 可表示为「一百二十一」。这样,只需规定「九」以后的发音,即可将数制拓展表示。

由于汉语的方言众多,若直接为「九」以后的数字规定发音,难以包括所有方言;但是,现代汉语的各方言大多由中古汉语分化而来,因此,只需要规定「九」以后的数字的中古汉语读音,其在现代汉语各方言中的读音都可以经推导得到。同时,日语、韩语和越南语中的汉字音都可以与中古汉语建立一定的对应关系,故使用中古汉语规定「九」的后继数字的发音,这套规则还可以在一定程度上应用于上述三种语言。

中古汉语的音系以《广韵》为代表。为了使得到的发音符合《切韵》音系,妥当的做法是在《广韵》的已有的音节中规定「九」的后继数字的读音。另外,若规定读音时依照一定的顺序,这样的读音必然不够自然;若完全采用随机方法,又不具有可重复性,因此,我们采用伪随机数解决该问题。Haskell 语言提供了生成随机数的单子——random 单子和用于求值的 evalRand 函数,只要每次使用相同的随机数种子,在相同的随机数生成方式下必然得到相同的结果。至此,问题变为在给定的区间——《广韵》中的小韵数——使用确定的随机数种子随机生成数值序列的问题。

在上述过程中,我们只是为「九」的后继数字规定中古汉语的「音」,但没有制定一个与该发音相对应的文字。但是,为了实际的操作方便,对于每个「九」的后继数字,我们仍然选择了《广韵》中与该「音」对应的其中一个汉字作为代表字。这样做的好处有:使规定的发音可以用文字书写,便于记录和记忆;可以以规定的代表字作为依据,上推上古汉语的发音;对于某些方言,若某个数字的发音无法确定,也可以据此确定读音。当然,这些代表字的写法肯定不像「一」到「九」的写法那样简便,不过我们也可以将这些代表字理解为类似「壹」到「玖」的大写数字,从而达到风格上的统一。

实现方法

首先由 Haskell 语言生成足够长度的随机数序列,随机数种子设定为 42。

已知「零」到「九」对应的《广韵》小韵号分别为 939、3290、2226、1090、2230、1381、3203、3291、3430 与 1933。将这些小韵号作为被选择的小韵的数组的初始值。

从随机数序列的第一个数开始,如果这个数对应的《广韵》小韵与任何一个已被选择的小韵的相似度小于给定值,则舍弃这一随机数;否则,将这一随机数加入被选择的小韵数组的末尾。这样做是为了避免选出的汉字中出现读音相近的字。

在被选择的小韵数组达到足够长度后,从每个小韵中选出一个字作为代表字。选择代表字的标准为:尽量不选太常用的字,否则在文本中会与该字的本义发生混淆;尽量不选太生僻的字,因为难于记忆和书写;尽量不选 Unicode 编码为拓展 B 区及以后的字,因为在某些旧系统中无法正常显示。

在选出代表字后,还要规定这些字的读音。规定读音时的标准如下:

  1. 如果该字有现代读音,且不是多音字,则选取该音
  2. 如果该字有现代读音,且是多音字,则首先根据选出该字时使用的《广韵》小韵推导现代读音,再从现代的多个读音中选取一个最接近推导读音的发音
  3. 如果该字没有现代读音,则规定为选出该字时使用的《广韵》小韵推导的现代读音

另外,在《切韵》音系中发音不同的字,在现代也可能变得相同。如果发生读音相同的情况,则用最自然的方式将后一个字改读为其他音。

我们认为,这样的规定读音的方式是最合理的。

有人可能认为,对于多音字,应该严格使用《广韵》小韵推导的现代读音,不应参考现代读音。我们认为,对于多音字,参考现代读音是更加合理的。在语言演化的过程中,多音字读音合并的现象十分常见,例如「疏」字在《广韵》中有「所葅切」与「所去切」两种读法,而普通话仅保留了「所葅切」对应的 shū 音;多音字的多个读音之间也可能相互影响,例如「曼」字在《广韵》中有「母官切」与「無販切」两种读法,分别对应 mánwàn,但随着语言的演化,在普通话中只有唯一的读音 màn

还有人可能认为,在发生读音相同的情况时,如果将后一个字改读为其他音,会破坏语言的系统性。实际上,这种现象在语言的演化中是十分常见的,并不能认为是破坏了语言的系统性。例如按照演化规律,普通话「炎」和「癌」应该同读为 yán,为了避免发生混淆,将「癌」字读为 ái12;粤语「不」读 bat¹,按照演化规律「必」也应该读 bat¹,但为了避免同音而读为 bit¹3

实现步骤与结果

下载常用字頻序表,将前 3000 字保存为 first3000.txt,其余字保存为 after3000.txt

编写代码,生成随机数序列。将以下代码保存为 random.hs

#!/usr/bin/env runhaskell

import Control.Monad.Random

dice :: RandomGen g => Int -> Rand g [Int]
dice n = sequenceA $ replicate n $ getRandomR (1, 3874)

main :: IO ()
main = print $ evalRand (dice 80) (mkStdGen 42)

运行,得到随机数序列:

$ ./random.hs
[2468,3238,3708,3811,3223,3064,2479,938,3137,3222,2346,1470,2329,3263,1479,1142,520,34,2354,843,2159,1193,2471,1707,81,633,767,2707,2404,2537,2103,1884,1261,409,919,2532,129,1071,1869,712,229,2957,3797,1899,3453,1407,738,2249,3288,1241,593,2587,1415,341,1636,822,1379,1548,1919,651,1718,3791,1602,1005,3462,3653,1095,1008,1255,686,1612,936,3096,3522,1047,579,2750,2953,3205,3099]

使用 qieyun-js 加载切韵音系数据:

$ npm install qieyun@0.4.0

编写代码。将以下代码保存为 index.js

'use strict';

const fs = require('fs');
const Qieyun = require('qieyun');

const randList = [2468, 3238, 3708, 3811, 3223, 3064, 2479, 938, 3137, 3222, 2346, 1470, 2329, 3263, 1479, 1142, 520, 34, 2354, 843, 2159, 1193, 2471, 1707, 81, 633, 767, 2707, 2404, 2537, 2103, 1884, 1261, 409, 919, 2532, 129, 1071, 1869, 712, 229, 2957, 3797, 1899, 3453, 1407, 738, 2249, 3288, 1241, 593, 2587, 1415, 341, 1636, 822, 1379, 1548, 1919, 651, 1718, 3791, 1602, 1005, 3462, 3653, 1095, 1008, 1255, 686, 1612, 936, 3096, 3522, 1047, 579, 2750, 2953, 3205, 3099];

const first3000 = new Set(fs.readFileSync('first3000.txt', 'utf8'));
const after3000 = new Set(fs.readFileSync('after3000.txt', 'utf8'));

const srs = [939, 3290, 2226, 1090, 2230, 1381, 3203, 3291, 3430, 1933];

function srSimilarity(a, b) {
    let score = 0;
    if (Qieyun.get母(a) != Qieyun.get母(b)) score += 1;
    if (Qieyun.get開合(a) != Qieyun.get開合(b)) score += 1;
    if (Qieyun.get等(a) != Qieyun.get等(b)) score += 1;
    if (Qieyun.get韻賅上去入(a) != Qieyun.get韻賅上去入(b)) score += 1;
    if (Qieyun.get聲(a) != Qieyun.get聲(b)) score += 1;
    return score;
}

while (srs.length < 64) {
    const nextRand = randList.shift();
    if (!srs.some((sr) => srSimilarity(nextRand, sr) <= 1)) {
        srs.push(nextRand);
    }
}

function selectBestChar(xs) {
    let highestEver = 0, choice;
    for (const x of xs) {
        let score = 1;

        if (!first3000.has(x)) score += 1;
        if (after3000.has(x)) score += 1;
        if (x.length == 1) score += 1;

        if (score > highestEver) {
            highestEver = score;
            choice = x;
        }
    }
    return choice;
}

console.log('小韻號:' + JSON.stringify(srs.slice(10)));
console.log('漢字:' + srs.slice(10).map((sr) => selectBestChar(Qieyun.query小韻號(sr).map((字頭_解釋) => 字頭_解釋[0]))).join(''));

运行,得到结果:

$ node index.js
小韻號:[2468,3238,3811,3064,2479,3137,2346,1470,2329,3263,1479,1142,520,843,2159,1193,2471,1707,633,767,2707,2404,2537,2103,1884,409,919,129,1869,712,229,2957,1899,3453,1407,738,2249,1241,593,2587,1415,341,1636,822,1548,1919,651,1718,3791,1602,1005,1255,686,3096]
漢字:䵘熇灄詬譺殮菟矧緅粟窘鵮頇穅帔耩膪鮑恮硨驠裔瀣仲逞礥鯖詑瑒魛裾絎炯鴰堄娿㿷跬鋗捃㢊𢰇諞邙齦拯迢堖遪銑哹兕橇譖

其中,由于「殮」字意义较为负面,使用同小韵的「瀲」字代替。

然后人工规定普通话读音如下:

  1. èr
  2. sān
  3. liù
  4. jiǔ
  5. shài
  6. shè
  7. gòu
  8. ài
  9. liàn
  10. shěn
  11. zōu
  12. jiǒng
  13. qiān
  14. hān
  15. kāng
  16. pèi
  17. jiǎng
  18. chuài
  19. bào
  20. zhuān
  21. chē
  22. yàn
  23. xiè
  24. zhòng
  25. chěng
  26. xín
  27. qīng
  28. tuó
  29. chàng
  30. dāo
  31. háng
  32. xiòng
  33. guā
  34. 娿 ē
  35. cuó
  36. kuǐ
  37. xuān
  38. jùn
  39. ǎi
  40. 𢰇
  41. piǎn
  42. máng
  43. yín
  44. zhěng
  45. tiáo
  46. nǎo
  47. qiāo
  48. zèn

(初稿作于 2017 年 9 月 27 日,定稿于 2020 年 3 月 15 日,修订于 2020 年 3 月 18 日)

注:由于本文初稿作于 2017 年 9 月 27 日,为阿万音铃羽诞生之日,因此特地将本系统命名为「铃羽系统」。

阿万音铃羽诞生之日哔哩哔哩网站的截图

源代码


  1. 必须区分的两个字如果按规律应该演变成同音字,那母语者通常会怎么处理? - 王伟超Mijiag的回答 https://www.zhihu.com/question/314764789/answer/615293621↩︎

  2. 如何評價臺灣教育部門字典認定「尷尬」也可以讀作 jiān jiè? - 谭樊马克的回答 https://www.zhihu.com/question/56672682/answer/189128061↩︎

  3. 必须区分的两个字如果按规律应该演变成同音字,那母语者通常会怎么处理? https://www.zhihu.com/question/314764789↩︎