← 返回博客

个人博客

把 GB/T 17710 写成代码:8 个算法 + 选型指南(GB/T 17710 续三)

前几篇把 GB/T 17710 的公式和人工手算讲透了。这一篇把它落成可运行的代码——一套覆盖标准全部 8 个算法的 Python / JavaScript 库,并附上选型指南:你的场景到底该用哪个算法。所有算法都用公开测试向量验证过(Eurocode 血袋号、gb-util 库、科普中国例题),两个语言版本输出完全一致。

一、代码长什么样

标准只有两条主干逻辑:纯系统用"加权求和取模"(多项式法),混合系统用"乘积折叠"(递归法)。库里把它们各自封装成一个类,再用参数实例化出 8 个算法。

纯系统 MOD 11-2 的核心(Python):

def calc_mod112(data: str) -> str:
    n = 0
    for i, ch in enumerate(reversed(data)):
        n += int(ch) * (2 ** i)          # 最右位权重 2^0
    v = (1 - 2 * n) % 11                 # 校验值
    check = "X" if v == 10 else str(v)   # 10 用 X 表示
    return data + check

混合系统 MOD 11,10 的核心(乘积折叠):

def calc_mod1110(data: str) -> str:
    p = 10
    for ch in data:
        d = int(ch)
        s = (d + p) % 10
        if s == 0:
            s = 10
        p = (s * 2) % 11
    return data + str((1 - p) % 10)

完整的库把 8 个算法都接好了 calc / is_valid / validate 接口,并带单元测试。上面的片段只是"最小可读版",真实库还处理了字母字母数字映射、2 位校验拆分、负数取模等细节。

二、8 个算法的"身份证"

按"字符集 + 校验位数"把 8 个算法摊开(博客渲染不支持表格,用分组清单):

纯系统(单模数,加权求和)

  • MOD 11-2:数字 0-9(+X),1 位,模 11、基 2
  • MOD 37-2:0-9、A-Z(+*),1 位,模 37、基 2
  • MOD 97-10:数字,2 位,模 97、基 10
  • MOD 661-26:纯字母 A-Z,2 位,模 661、基 26
  • MOD 1271-36:0-9、A-Z,2 位,模 1271、基 36

混合系统(双模数,乘积折叠,乘数统一为 2)

  • MOD 11,10:数字,1 位(M=10)
  • MOD 27,26:纯字母 A-Z,1 位(M=26)
  • MOD 37,36:0-9、A-Z,1 位(M=36)

关键点:混合系统在标准里只有这三种,且都只出 1 位。需要 2 位校验时,标准走的是纯系统(MOD 97-10 等),不是混合。

三、选型指南:到底用哪个

一句口诀:先看字符集,再看要几位,最后看能不能接受补字符。

  • 只用数字、1 位、能接受 X 补字符 → MOD 11-2(最经典,与身份证同族)
  • 只用数字、1 位、校验位必须还是数字(不能出现 X)→ MOD 11,10(税号、OIB、血袋号都用它)
  • 只用数字、要 2 位 → MOD 97-10(IBAN 同思路、强度更高)
  • 字母数字、1 位、**能接受 * 补字符 → MOD 37-2**
  • 字母数字、1 位、校验位必须还是字母数字 → MOD 37,36
  • 纯字母、1 位 → MOD 27,26
  • 字母数字、要 2 位 → MOD 1271-36
  • 纯字母、要 2 位 → MOD 661-26

混合系统的价值,正是"校验字符永远留在原生字符集"——当你规定标识符里不能出现 X、* 这类补字符时(税号、血袋号、ISBT 都是如此),它就是唯一选择。

四、验证:拿公开向量过一遍

库里的测试不是自己编的,而是对照公开资料:

  • MOD 11,10 血袋号 "0823" → "08235"(Eurocode 例题)
  • MOD 11-2 "0794" → "07940"(科普中国例题)
  • MOD 37-2 "1234ABCD" → "V"、MOD 97-10 "794" → "44"、MOD 661-26 "ABCD" → "KN"、MOD 1271-36 "ISO79" → "3W"(gb-util 库向量)
  • MOD 27,26 "JEJLMGJ" → "S"、MOD 37,36 长串 → "I"

两个语言版本都跑通了全部用例,并且能正确拦截"把某一位写错"的脏数据(篡改后 is_valid 返回 False)。

五、怎么用

Python:

from iso7064 import mod11_2, mod37_36
mod11_2.calc("0794")                       # "07940"
mod11_2.is_valid("07940")                  # True
mod37_36.calc("B7Q3SFTUSH2QN7BIXBPMNZAM")  # 末尾补 "I"

JavaScript(ES Module):

import { mod11_10, mod1271_36 } from "./iso7064.js";
mod11_10.calc("0794");          // "07945"
mod1271_36.is_valid("ISO793W"); // true

完整代码与测试随本文提供(Python + JS 双版本),直接拿去用即可。