Luhn算法检验和验证
一、Luhn公式介绍
Luhn公式是一种广泛使用的系统,用于对标识号进行验证。它根据原始标识号,把每隔一个数字的值扩大一倍。然后把各个单独数字的值加在一起(如果扩大一倍后的值为2个数字,就把这两个数字分别相加)。如果相加之后可以被10整除,那么这个标识号就是合法的。
编写一个程序,接受一个任意长度的标识号,并根据Luhn公式确定这个标识号是否合法。这个程序在读取下一个字符之前必须处理之前所读取的那个字符。
过程有些复杂,在此上传一张图片以供各位理解:

记住:最终标识号的检验和应该能够被10整除,或者说应该以0结尾。
二、问题分步求解
- 知道哪些数字需要扩大一倍。
- 对扩大一倍后大于等于10的数字,根据他们的单独数字进行处理。
- 知道已经到达了标识号的尾部。
- 分别读取每个数字。
(注:我们不需要按照特定的顺序处理这些问题。)
首先,我们处理扩大一倍后大于或等于10的数:
如果我们从单独的数字0~9开始并把它们扩大一倍,最大值将是18。因此,一共只有两种可能性:如果扩大一倍后的值为单个数字,就不需要再做处理;如果扩大一倍后的值大于或等于10,它的范围肯定在10~18之间,因此第一个数字总是为1.我们通过一个代码来验证一下:
int digit;
printf("Enter a single digit number,0-9:");
scanf("%d",&digit);
int doubledDigit = digit * ; //程序读取数字,并把它的值扩大一倍
int sum;
if(doubledDigit >= )
sum = + doubledDigit % ; //求和计算
else
sum = doubledDigit;
printf("Sum of digits in doubled number:%d\n",sum); //输出求和结果
验证结果如下:

我们可以把这段代码转化为一个短小的函数,这样就可以简化未来的代码了。(是不是很有远见呢?)
int doubleDigitValue(int digit)
{
int doubledDigit = digit * ; //程序读取数字,并把它的值扩大一倍
int sum;
if(doubledDigit >= )
sum = + doubledDigit % ; //求和计算
else
sum = doubledDigit;
return sum;
}
现在,我们读取标识号的单独数字:
如果我们以数值类型(例如int)的形式读取标识号,将会读取一个长长的数,需要处理很多事情。另外,可以读取的最大整数也是有限制的。但在该问题中,标识号可以是任意长度的。因此,我们必须逐字符读取。这意味着我们要知道怎样读取一个表示数字的字符并把它转换为整数类型,以便对它进行数学运算。来看以下代码:
char digit;
printf("Enter a one-digit number:");
scanf("%c",&digit);
int sum = digit;
printf("Is the sum of digits:%d?\n",sum);
运行结果为:

字符7是以字符码值55存储的,因此当我们把这个字符作为整数时,得到的结果就是55.
因此,我们需要一种机制把字符7转换为整数7。
我们可以创建一张表,其中包含原值和目标值,还有两值之间的误差。
| 字符 | 字符码 | 目标整数值 | 差 |
| 0 | 48 | 0 | 48 |
| 1 | 49 | 1 | 48 |
| 2 | 50 | 2 | 48 |
| 3 | 51 | 3 | 48 |
| 4 | 52 | 4 | 48 |
| 5 | 53 | 5 | 48 |
| 6 | 54 | 6 | 48 |
| 7 | 55 | 7 | 48 |
| 8 | 56 | 8 | 48 |
| 9 | 57 | 9 | 48 |
字符码和目标整数值之差始终是48,因此我们需要做的就是使字符码减去这个值。而48正好是0的字符码,所以我们可以采用一种更通用、更容易理解的解决方案:就是减去字符0的字符码而不是减去像48这样预先确定的值:
char digit;
printf("Enter a one-digit number:");
scanf("%c",&digit);
int sum = digit - '';
printf("Is the sum of digits:%d?\n",sum);
运行结果为:

现在,我们转到问题的下一部分,判断哪些数字需要扩大一倍:
我们可以先试着把长度限制为6,则我们只需要读取6个数字,对它们进行求和,然后判断它们的和是否被10所整除,代码如下:
char digit;
int checksum = ;
printf("Enter a six-digit number:");
for(int position = ;position <= ;position++){
scanf("%c",&digit);
checksum += digit - '';
}
printf("Checksum is:%d\n",checksum);
if(checksum% == )
printf("Valid:Checknum is divisible by 10\n");
else
printf("Invalid:Checknum is not divisible by 10\n");
运行结果为:

现在,我们需要为实际的Luhn检验公式增加逻辑,把从左边开始位置为奇数的数字扩大一倍。我们可以使用求摸操作符(%)确定奇数和偶数的位置,因为偶数的定义是它能够被2所整除。因此如果表达式位置%2的结果是1,这个位置就是奇数,应该把它扩大一倍。顺便插一句,在扩大一倍后,如果结果大于或等于10,还需要对这个结果的各个数字进行求和。代码如下(只需把for循环那改一下):
for(int position = ;position <= ;position++){
scanf("%c",&digit);
if(position% == ) checksum += digit - '';
else checksum += doubleDigitValue(digit - '');
}
运行结果为:

到目前为止,我们在这个问题上已经取得很大的进展,但还需要完成一些步骤才能为任意长度的标识号编写代码。为了最终解决这个问题,我们需要采用分治法。
先考虑怎样处理长度为任意偶数的标识号。
我们所面临的第一个问题是怎样确定已经到达了标识号的末尾。如果用户输入了一个多位的标识号又按下了Enter键表示结束,并且我们是逐个字符读取输入的,那么在最后一个数字之后所读取的字符是什么呢?我们不妨用代码来试验一下:
printf("Enter a number:");
char digit;
while(){
scanf("%c",&digit);
printf("%d\n",int(digit));
}
运行结果为:

输入1234,结果是49 50 51 52 10(结果基于ASCII码)。从运行结果中可以看出,10就是我们所寻找的结果,所以我们可以在前面的代码中用一个while循环代替for循环:
//处理任意偶数长度的标识号
char digit;
int checksum = ;
int position = ;
printf("Enter a number with an even number of digits:");
scanf("%c",&digit); //读取第一个值
while(digit != ){ //用来检查字符码的值是否为行末符
if(position% == ) //偶数位判断
checksum += digit - '';
else checksum += * (digit - '');
scanf("%c",&digit); //读取每个后续的值
position++;
}
printf("Checksum is:%d\n",checksum);
if(checksum% == )
printf("Valid:Checksum is divisible by 10\n");
else
printf("Invalid:Checksum is not divisible by 10\n");
运行结果为:

现在已经解决了“怎样确定已经到达了标识号的末尾”的问题。
要穷尽每种可能性,标识号的长度必须是奇数或者偶数。如果我们预先知道长度,就可以知道应该把奇数位的数字或者偶数位的数字扩大一倍。但是,在读取完这个标识号之前,我们并不知道这个信息。在思考这个问题前,我们先来类比另外一个问题:
编写一个程序,从用户那里读取10个整数。在输入了所有的整数之后,要求显示这些数中正数或负数的数量。
编写思路:需要一个对正数进行计数的变量,并用另一个变量对负数进行计数。当用户在程序的最后指定了具体的请求时,只需显示适当的变量作为响应即可。代码如下:
int number;
int positiveCount = ;
int negativeCount = ;
for(int i = ;i <= ;i++){
scanf("%d",&number);
if(number > ) positiveCount++; //计数正值
if(number < ) negativeCount++; //计数负值
}
char response; //选择回答
printf("Do you want the (p)ositive or (n)egative count?");
getchar(); //吞掉回车
scanf("%c",&response);
if(response == 'p')
printf("Positive Count is %d\n",positiveCount);
if(response == 'n')
printf("Negative Count is %d\n",negativeCount);
运行结果为:

这个类比的问题显示了我们在解决Luhn检验和问题时所需要用到的方法:同时以两种方式追踪当前的检验和,分别是在标识符为奇数长度和偶数长度的情况下。当我们读取完这个编号并确定了它的真正长度时,再选择表示正确的检验和的变量。
现在,我们可以把所有的代码都集中在一起,来解决这个问题了。
三、完整代码
char digit;
int oddLengthChecksum = ;
int evenLengthChecksum = ;
int position = ;
printf("Enter a number:");
scanf("%c",&digit);
while(digit != ){
if(position% == ){
oddLengthChecksum += doubleDigitValue(digit - '');
evenLengthChecksum += digit - '';
}
else{
oddLengthChecksum += digit - '';
evenLengthChecksum += doubleDigitValue(digit - '');
}
scanf("%c",&digit);
position++;
}
int checksum;
if((position - )% == ) checksum = evenLengthChecksum;
else checksum = oddLengthChecksum;
printf("Checksum is:%d\n",checksum);
if(checksum% == )
printf("Valid:Checknum is divisible by 10\n");
else
printf("Invalid:Checknum is not divisible by 10\n");
运行结果为:

感受
这篇博文写了一晚上,视力开始模糊了,而且还有一些头痛的症状,可能是昨天下午出去玩吹凉风了。不过今天还是很开心的,看着一个完整的算法被我们切成一小块一小块的细致分析和代码检验,沉浸于其中,一点点的接近真相,我感到兴奋和快乐!刚开始我还对函数调用和程序中的回车问题有所疑惑,不过在一位朋友的指点下我还是顺利通过了。最重要的是,我对这个算法也有了更深一步的了解与认识。
Luhn算法检验和验证的更多相关文章
- Object-C 银行卡,信用卡校验规则(Luhn算法)
最近的项目中涉及到绑定用户的银行卡,借记卡.经过查找银行卡的校验规是采用 Luhn算法进行验证. Luhn算法,也被称作“模10算法”.它是一种简单的校验公式,一般会被用于身份证号码,IMEI号码,美 ...
- JavaScript实现LUHN算法验证银行卡号有效性
一般验证银行卡有效性用到一种叫做LUHN的算法,简介请参考这篇博客:基于Luhn算法的银行卡卡号的格式校验 注意: 1.LUHN算法只是能校验卡号是否有效,并不能校验卡号和用户名是否一致. 2.如果有 ...
- 使用Luhn算法实现信用卡号验证
问题描述: 2:信用卡号的验证 [信用卡号的验证] 当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?其实可以不必这么 担心,因为并不是一个随便的信用卡号码都是合法的,它必须通过 Luhn 算法 ...
- PHP LUHN算法验证银行卡
<?php /* 16-19 位卡号校验位采用 Luhn 校验方法计算: 第一步:把信用卡号倒序(61789372994) 第二步:取出倒序后的奇数位置上的号码, 相加等到总和s1.(eg:s1 ...
- PHP中使用Luhn算法校验信用卡及借记卡卡号
Luhn算法会通过校验码对一串数字进行验证,校验码通常会被加到这串数字的末尾处,从而得到一个完整的身份识别码. 我们以数字“7992739871”为例,计算其校验位: 从校验位开始,从右往左,偶数位乘 ...
- LUHN算法
LUHN算法,主要用来计算信用卡等证件号码的合法性. 1.从卡号最后一位数字开始,偶数位乘以2,如果乘以2的结果是两位数,将两个位上数字相加保存. 2.把所有数字相加,得到总和. 3.如果信用卡号码是 ...
- 判断用户输入的银行卡号是否正确--基于Luhn算法的格式校验
开发中,有时候,为了打造更好的用户体验,同时减轻服务器端的压力,需要对于一些如,手机号码,银行卡号,身份证号码进行格式校验 下面是判断银行卡号输入是否正确的代码(基于Luhn算法的格式校验): iOS ...
- 银行卡号码校验算法(Luhn算法,又叫模10算法)
有时候在网上办理一些业务时有些需要填写银行卡号码,当胡乱填写时会立即报错,但是并没有发现向后端发送请求,那么这个效果是怎么实现的呢. 对于银行卡号有一个校验算法,叫做Luhn算法. 一.银行卡号码的校 ...
- [技术栈]C#利用Luhn算法(模10算法)对IMEI校验
1.Luhn算法(模10算法) 通过查看ISO/IEC 7812-1:2017文件可以看到对于luhn算法的解释,如下图: 算法主要分为三步: 第一步:从右边第一位(最低位)开始隔位乘2: 第二步:把 ...
随机推荐
- Java中return返回结果的优先级
在Java开发时,异常处理是非常普遍的.先看这样一道关于异常处理的代码 public static int getNumer() { int a = 1; try { return a; } catc ...
- Java 必须掌握的 12 种 Spring 常用注解!
1.声明bean的注解 @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller ...
- Linux系统性能监控之6个vmstat和6个iostat命令
这篇文章主要介绍一些Linux性能检测相关的命令. vmstat和iostat的两个命令可以运行在主流的Linux/Unix操作系统上. 如果vmstat和iostat命令不能再你的电脑上运行,请安装 ...
- c++map的用法
Map是c++的一个标准容器,她提供了很好一对一的关系,在一些程序中建立一个map可以起到事半功倍的效果,总结了一些map基本简单实用的操作!1. map最基本的构造函数: map<stri ...
- Ubantu 使用extundelete恢复数据
所以在维护系统的时候,要慎之又慎,但是有时难免会出现数据被误删除的情况,在这个时候改如何快速.有效地恢复数据呢?本文我们就来介绍一下Linux系统下常用的几个数据恢复工具. 一.如何使用“rm -rf ...
- 《Android 编程权威指南》读书总结
1.当一段代码被多次使用,可将这段代码封装成一个抽象类,以后再要用到该段代码时,直接extends(继承)这个抽象类. 2.SDK版本向后兼容,即在SDK发布后推出的Android版本都可以使用该SD ...
- tf.constant
tf.constant constant( value, dtype=None, shape=None, name='Const', verify_shape=False ) 功能说明: 根据 val ...
- java中常用的16个工具类
1. org.apache.commons.io.IOUtils:处理io流的相关操作 closeQuietly ( ) toString ( ) copy ( ) toByteArray ( ) w ...
- win7+python3.6+word_cloud 安装出现Microsoft Visual C++ 14.0 is required
说明 环境: 已安装Anaconda3 (64-bit) 4.4.0(Python 3.6.1).其中,代码调试在Spyder 3.1.4中进行,安装包则直接打开Anaconda Prompt调用cm ...
- python pandas ---Series,DataFrame 创建方法,操作运算操作(赋值,sort,get,del,pop,insert,+,-,*,/)
pandas 是基于 Numpy 构建的含有更高级数据结构和工具的数据分析包 pandas 也是围绕着 Series 和 DataFrame 两个核心数据结构展开的, 导入如下: from panda ...