不同的编号体系看上去各写各的规矩,但把校验位算法摊开比对,会发现它们共享同一个骨架:把号码前面的位按位加权、求和、对某个模数取余,再把余数或它的补数变成最后那一位。所谓算法不同,多数时候只是权重、模数与映射方式这三处参数不同。本文按家族成员来对比,让你在需要选一支时知道该问什么。
校验位算法家族有哪几支?
按模数分,最常被引用的成员集中在三支上。第一支是 mod-10,也就是对十取模,其中有一路做法会先把一部分位加倍再求和,银行卡号使用的就是这一支,它只在「末位由前面所有位决定」这个层级上属于家族,具体规则另有专篇。第二支是 mod-11,对十一取模,因为能区分出更多余数,抓错能力更强。第三支是 mod-97,模数大,适合位数很长的号码。
为什么模数决定成员划分而不是权重决定?因为模数决定了余数的可能取值范围,也就决定了校验位能承载多少信息。模 10 只能得到十种余数,映射成一位十进制数字刚好够用;模 11 会多出一种余数,于是需要额外规定它怎么写;模 97 的余数多得多,映射规则就成了标准里必须交代的一部分。
这三支并不是竞争关系。一个制度选哪一支,取决于它的号码有多长、字符集是纯数字还是字母数字混排、以及它更在意哪一类录入错误。
mod-10、mod-11 与 mod-97 的差别在哪里
三支的主要差别可以放在一张表里对照,注意这里只写家族层面的共性,各制度的具体参数一律以它自己公布的说明为准。
| 成员 | 模数 | 典型特征 | 需要注意的地方 |
|---|---|---|---|
| mod-10 | 10 | 校验位是一位十进制数字,映射最直接 | 余数空间小,某些换位错误抓不住 |
| mod-11 | 11 | 抓错能力更强,常配合递增或递减的权重 | 余数可能落到两位数,需要规定写法 |
| mod-97 | 97 | 适用于位数很长的号码,可对长串取余 | 计算过程需要先按规则重排与替换字符 |
共同骨架之下,差别集中在三处:权重怎么取、模数是多少、余数怎么变成最后一位。理解这三处,比背下某一种写法的公式有用得多,因为你在系统里真正要做的判断是「这个制度的算法属于哪一支,我该按哪份公开说明去实现」。
加权与取模:家族内部的参数怎么变
权重是一串与位置对应的乘数。可以选择固定权重,比如每一位都乘同一个数;也可以选择递增或递减的权重;还可以选择交替权重,让相邻两位的乘数不同。权重怎么排,直接决定这支算法对哪类错误敏感。
模数决定余数的空间。模数越大,能区分的余数越多,对不同输入落在同一结论上的概率越低,但映射规则也越复杂。当余数可能出现两位数时,标准必须额外规定它写成一个数字还是某个字母,有的体系就明确用字母 X 代表其中一种余数。这类约定不是细节,而是你在实现时最先撞上的现实约束。
求和之后还有一种常见变体:不直接使用余数,而使用它的补数。两者在数学上等价,但在实现细节上会带来一个容易写错的地方——补数计算中的取模边界,稍不留神就会让整支算法在某个特定输入上偏一位。
为什么有的算法抓得住换位错误?
单字符写错几乎任何一支都能抓。原因是改动一位数字会让加权和的余数发生变化,只要权重不为零,余数就守不住。真正拉开差距的是相邻两位互换。
两个相邻位互换后总和是否改变,取决于这两位的权重是否相等。如果权重对称,也就是相邻两位乘的是同一个数,那么互换前后各项之和完全一样,算法看不出任何异常。只有当权重不对称时,互换才会让加权和变化,算法才抓得住。这也是为什么很多制度宁可把权重排成递增或交替的形式,也要避开对称权重。
因此,把某支算法描述成「能发现所有录入错误」是不准确的。准确的表述是:它能发现大多数单字符错误,以及在权重不对称时发现相邻换位。
还有一类错误它同样抓不住:两位数字同时被改动,且改动量恰好互相抵消。要覆盖这种情形,只能靠位数更多的校验位或额外的业务核对,指望算法本身周全并不现实。
选算法时真正要问的四件事
选型不该从「哪个算法更强」开始,而该从四个具体问题开始。
- 号码有多长:短号码用大模数往往是浪费,长号码用小模数则区分度不够。
- 字符集是什么:纯数字与字母数字混排会影响重量与映射方式,字母要参与计算就必须先有固定的替换规则。
- 错误主要来自哪里:人工录入更需要防换位与漏位,机器生成的批量数据更在意计算是否稳定可复现。
- 校验位算成两位数时怎么表示:这是模 11 系列绕不开的一步,必须在规则里写死。
把四个问题回答清楚,剩下的选择空间通常只剩一两种。这时再看该制度或该行业有没有已经通行的做法,跟着走比自己发明安全得多。
给开发者:一个家族带来的实现统一
家族视角最大的好处是能让实现收敛。你可以把权重、模数与映射方式抽成三项参数,让同一段计算逻辑服务多支算法,而不是为每个制度各写一份长得差不多的循环。这样新增一个制度时,改动量通常只是填一组参数。
前提是不要自己发明规则。始终按制度公布的说明实现,再用该制度公布的样例自检:拿一组已知成立的号码跑一遍,结论必须与公布结果一致;更换任何一个参数之后,同一样例应当开始失败,这能证明参数真的被用上了。
还要留意「不同制度同名不同参」的情况。两个制度都写 mod-11,权重却可能完全不同,直接把名字当成参数来查表会出错。到 IBAN 那一支时这种分层更明显,见IBAN 结构与 mod-97。如果只是想先弄清三层过滤的顺序,号码校验的原理那篇更基础。银行卡号那一支的完整规则不在本文展开,可对照Luhn 算法阅读。
下一步
想验证某一串输入在家族里的哪一支上成立,把号码粘进号码校验工具,看它给出的依据属于哪一套规则,再回头查那份公开说明。选型阶段最省事的做法,是先抄一份该制度公布的样例做回归,再谈参数调优。
如果你在做数据准备而不是选型,证件号校验位那篇从测试数据的角度讲了同一批算法的用法。
本文提到的算法参数属于公开知识,但文中出现的任何示例数字都是为说明计算思路而虚构构造的,不代表任何制度真正发放过的号码,也不构成对任何号码真实性或可用性的判断。