TearSnow Fan


解读身份证号编码和最后一位校验码计算

我国现行使用公民身份证号码有两种尊循两个国家标准,即〖GB 11643-1989〗和〖GB 11643-1999〗。

其中,〖GB 11643-1989〗中规定的是15位身份证号码,排列顺序从左至右依次为:

6位数字地址码,6位数字出生日期码,3位数字顺序码,出生日期年份为2位,因此无法区分1900和2000等。(目前该算法已淘汰)

GB 11643-1999〗中规定的是18位身份证号码,公民身份号码是特征组合码,由十七位数字本体码一位数字校验码组成。排列顺序从左至右依次为:

6位数字地址码,8位数字出生日期码,3位数字顺序码 和 1位数字校验码。

1.地址码表示编码对象常住户口所在县(市、旗、区)的行政区划代码;

2.生日期码表示编码对象出生的年、月、日,其中年份用4位数字表示,年、月、日之间不用分隔符;

3.顺序码表示同一地址码所标识的区域范围内,对同年、月、日出生的人员编定的顺序号,顺序码的奇数分给男性,偶数分给女性;

4.校验码是根据前面十七位数字码,按照〖ISO 7064:1983〗.MOD 11-2校验码计算出来的检验码。

下面先讨论下这个校验位的计算。

公式如下:

∑(a[i]*W[i]) mod 11 ( i = 2, 3, ..., 18 )

* --------- 表示乘号
i --------- 表示身份证号码每一位的序号,从右至左,最左侧为18,最右侧为1.
a[i] ------ 表示身份证号码第 i 位上的号码
W[i] ----- 表示第 i 位上的权值 W[i] = 2^(i-1) mod 11

然后设上面的 求和结果为 R

根据下表找出 R 对应的校验码即为要求身份证号码的校验码C.

R  0  1  2  3  4  5  6  7  8  9  10

C  1  0  X  9  8  7  6  5  4  3   2

由此看出 X 就是代表10,罗马数字中的 10 就是X,所以在新标准的身份证号码中可能含有非数字的字母X。

举一例进行说明:

ID No. —– 输入的身份证号

位权值 —– 由公式 W[i] = 2^(i-1) mod 11 得出值,如第一位的位权值为7=Mod(2^(18-1),11)

加权和 —– ∑(a[i]*W[i]) ,身份证第i位上的号码乘以第i位的位权值后相加之和.

 Code  —– 加权和 Mod 11 对就的C值(即为检验码

单击图片查看大图,具体计算步骤和公式详见文附件“身份证校验工具.xlsx”的 Sheet2。


18位身份证号校验位计算的C语言实现(未经亲自验证):

#include
#include
#include
#include

int IsDigitBuf(char *sBuf, int nLen)
{
    int    i;

    if (nLen == 0) return 1;
    if (nLen > strlen(sBuf)) nLen = strlen(sBuf);

    for (i = 0; i < nLen; i++)
        if (!isdigit(sBuf[i])) return 0;

    return 1;
}

int checkdate(int iYear, int iMonth, int iDay)
{
    if (iYear < 0 || iYear > 9999)
    return -1;
    switch (iMonth)
    {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        if (iDay <= 0 || iDay > 31)
        {
            return -3;
        }
        break;
    case 4:
    case 6:
    case 9:
    case 11:
        if (iDay <= 0 || iDay > 30)
        {
            return -3;
        }
        break;
    case 2:
        if ((iYear % 4 == 0 && iYear % 100 != 0) || iYear % 400 == 0)
        {
            if (iDay <= 0 || iDay > 29)
            {
                return -3;
            }
        }
        else
        {
            if (iDay <= 0 || iDay > 28)
            {
                return -3;
            }
        }
        break;
    default:
        return -2;
    }
    return 0;
}

int CheckStrDate(char *sDate)
{
    int iRet;

    char sYear[5];
    char sMonth[3];
    char sDay[3];

    memset(sYear, 0, sizeof(sYear));
    memset(sMonth, 0, sizeof(sMonth));
    memset(sDay,   0, sizeof(sDay));

    if (strlen(sDate) != 8)
    {
        return -1;
    }

    memcpy(sYear, sDate, 4);
    memcpy(sMonth, sDate+4, 2);
    memcpy(sDay,   sDate+6, 2);

    iRet = checkdate(atoi(sYear), atoi(sMonth), atoi(sDay));
    if (iRet != 0)
    {
        return -1;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    int    i;
    int    iRet;
    int    iWeight[18] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
    char   cCheck[11] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
    char   sIdCardNo[20];
    char   sDate[8 + 1];
    int    iDate;
    int    Sum = 0;

    if (argc < 2) {         printf("FAIL\n");         exit(1);     }     memset(sIdCardNo, 0, 20);     memcpy(sIdCardNo, argv[1], 18);     if ((strlen(sIdCardNo) == 15) && ((iRet = IsDigitBuf(sIdCardNo, 15)) > 0)) {
        printf("OK\n");
        exit(0);
    } else if (strlen(sIdCardNo) != 18) {
        printf("FAIL\n");
        exit(1);
    }

    /* 身份证7-14位是否有效 */
    memset(sDate, 0, sizeof(sDate));
    memcpy(sDate, sIdCardNo + 6, 4);
    iDate = atoi(sDate);
    if (iDate < 1900 || iDate > 2020) {
        printf("FAIL\n");
        exit(1);
    }
    memset(sDate, 0, sizeof(sDate));
    memcpy(sDate, sIdCardNo + 6, 8);
    iRet = CheckStrDate(sDate);
    if (iRet < 0) {
        printf("FAIL\n");
        exit(1);
    }

    /* 身份证18位校验位是否有效 */
    for (i = 0; i < 17; i ++) {
        memset(sDate, 0, sizeof(sDate));
        sDate[0] = sIdCardNo[i];
        iDate = atoi(sDate);
        Sum += iWeight[i] * iDate;
    }
    Sum %= 11;
    if ('x' == sIdCardNo[17]) {
        sIdCardNo[17] = 'X';
    }
    if (cCheck[Sum] != sIdCardNo[17]) {
        printf("FAIL\n");
        exit(1);
    }

    printf("OK\n");
    exit(0);

}

Excel 函数实现

身份证号码校验也可以利用excel函数公式完成,假设需要验证的身份证号位于 B2 单元格。

一、18位身份证号码校验函数公式:
=IF(LEN(B2)=18,IF(MID("10X98765432",MOD(SUM(MID(LEFT(B2,17),ROW(INDIRECT("1:17")),1)*2^(18-ROW(INDIRECT("1:17")))),11)+1,1)=RIGHT(B2,1),"通过","校验未通过"),"")

二、15位身份证号码升18位函数公式:

=IF(LEN(B2)=15,REPLACE(B2,7,,19)&MID("10X98765432",MOD(SUM(MID(REPLACE(B2,7,,19),ROW(INDIRECT("1:17")),1)*2^(18-ROW(INDIRECT("1:17")))),11)+1,1),"")

三、同时完成15位升位或18位校验的函数公式:

=IF(LEN(B2)=18,IF(MID("10X98765432",MOD(SUM(MID(LEFT(B2,17),ROW(INDIRECT("1:17")),1)*2^(18-ROW(INDIRECT("1:17")))),11)+1,1)=RIGHT(B2,1),"通过","校验未通过"),IF(LEN(B2)=15,REPLACE(B2,7,,19)&MID("10X98765432",MOD(SUM(MID(REPLACE(B2,7,,19),ROW(INDIRECT("1:17")),1)*2^(18-ROW(INDIRECT("1:17")))),11)+1,1),""))

注意:以上公式中B2为身份证号码所在单元格,公式均为数组公式,需要录入公式后按 ctrl + shift + 回车结束公式,使公式处于{}括号之内方能计算正确,但不能直接在公式两边录入{} 。


附件列表:点击此处下载

附件1:身份证校验码计算器(bat批处理文件)

附件2:Excel身份校验模型(xlsx文件,Sheet1 对生日、性别等信息和校验码进行校验,Sheet2 演示了校验码的计算过程,很详细!)

.

参考资料:

[1] 身份证号编码之迷 [附送身份证校验工具]

[2] 身份证号码校验公式_excel函数公式实例

本文固定链接: http://blog.xieyc.com/id-number-and-check-digi/ | 小谢的小站

该日志由 xieyc 于2013年07月28日发表在 编程, 软件 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 解读身份证号编码和最后一位校验码计算 | 小谢的小站
关键字: ,

目前共有 1 条留言 【 访客:1 条, 博主:0 条 】 访客以 1:0 暂时领先博主!

  1. 沙发
    surda:

    整理的蛮好

    2013-08-02 21:27

发表评论

:wink: :neutral: :mad: :twisted: :smile: :shock: :sad: :roll: :oops: :eek: :mrgreen: :lol: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

快捷键:Ctrl+Enter