关于判断卡BIN的修正

博客指出程序存在两个错误,一是order by不能保证卡BIN按位数长度排列,二是memcmp处理看短不看长。并给出修改方案,一是将文本类型转换为数值类型排序,二是修改循环逻辑,按长的判断memcmp。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文连接:http://blog.youkuaiyun.com/yuanfen127/archive/2005/12/23/560633.aspx

程序有两个错误
第一
sprintf(l_tmpstr,"select * from CUPSCTL order by dc_preind");
由于dc_preind字段是CHAR(12 BYTE),因此这样的order by并不能保证卡BIN是按照位数长度排列的
第二
if
( 0 == memcmp(l_card_bin,local_cupsctl[j].dc_preind,i) )
这样处理是看短不看长,如果l_card_bin是955881,而local_cupsctl[j].dc_preind是95588111,但是由于i是l_card_bin的长度,因此仍然认为这个memcmp返回0

修改处理
一、sprintf(l_tmpstr,"select dc_preind,des,to_number(dc_preind) from CUPSCTL order by 3");
在选择的字段后面加上to_number(dc_preind)把这些文本类型转换成数值类型,再根据这个字段位置进行排序,确保按照卡BIN长度位数从少到多放入到内存数组中
二、整个循环只使用一个既可,修改如下:
   
/*错误的
    for ( i = 12 ; i > 5 ; i -- )
    {
        memset(l_card_bin,' ',sizeof(l_card_bin));
        memcpy(l_card_bin,v_tcard_no,i);        /*把卡号前i位复制给l_card_bin*/
 
        for ( j=local_cupsctl_cnt; j >= 0 ; j--)/*数组中的CUPSCTL是否有此BIN*/
        {
            if ( 0 == memcmp(l_card_bin,local_cupsctl[j].dc_preind,i) )
            {
                memcmp(v_cupsctl,&local_cupsctl[j],sizeof(CUPSCTL));
                stop_flag = 1;
                break;
            }
        }

        if ( 1 == stop_flag )
        {
            break;
        }
    }
    */

    memset(l_card_bin,' ',sizeof(l_card_bin));
    memcpy(l_card_bin,v_tcard_no,i);          /*把卡号前i位复制给l_card_bin*/

    for ( j=local_cupsctl_cnt-1; j >= 0 ; j--)/*数组中的CUPSCTL是否有此BIN*/
    {
        if ( 0 == memcmp(l_card_bin,local_cupsctl[j].dc_preind,
                             strlen(local_cupsctl[j].dc_preind)) ) /*按长的判断*/
        {
            memcpy(v_cupsctl,&local_cupsctl[j],sizeof(CUPSCTL));
            stop_flag = 1;
            break;
        }
    }

    if ( 1 == stop_flag )
    {
        break;
    }

<think>我们正在讨论信用验证算法,用户提供了多个引用,展示了不同语言实现的信用号有效性验证。这些实现都基于Luhn算法(也称为模10算法),这是一种简单的校验和公式,用于验证各种标识号码,如信用号、IMEI号码等。 核心需求:解释信用号有效性验证的算法原理,并展示实现。 Luhn算法步骤: 1. 从右向左,对每一位数字进行处理(包括校验位)。 2. 从右向左,对偶数位(按算法位置计数,从1开始)的数字进行加倍处理(如果从左边开始,则位置计数会不同,但通常实现从右开始)。 - 如果加倍后数字大于等于10,则减去9(或等价地,将两位数字相加)。 3. 将所有处理后的数字(包括未加倍的数字)相加,得到总和。 4. 如果总和能被10整除,则号有效;否则无效。 注意:不同实现中,计数方式可能不同(从左边还是右边开始计数)。但标准Luhn算法是从右向左计数,最右边第一位(校验位)不进行加倍,第二位加倍,以此类推。 我们参考用户提供的代码,可以总结如下: 在引用[1]的JavaScript代码中: - 将号字符串转化为数组(从右向左取每一位)。 - 然后遍历数组,索引i从0开始,那么实际上原号最右边的数字在数组索引0位置(即第一个元素)。 - 然后,它判断(i+1)%2==1,即索引为偶数的位置(因为i从0开始,i+1为奇数)对应原号从右向左数的奇数位(第1,3,5,...位)不加倍;而索引为奇数的位置(i+1为偶数)对应原号从右向左数的偶数位(第2,4,6,...位)需要加倍。 - 注意:这里数组索引0(原号最右边第一位)是奇数位(第1位),所以不加倍;数组索引1(原号右边第二位)是偶数位(第2位),需要加倍。 在引用[2]的Python代码中: - 它反转了字符串(从右向左处理),然后取索引0位置为最右边第一位(校验位),然后对索引为奇数的位置(即反转后的数组索引1,3,5,...)进行加倍(因为从0开始,0是第一位,1是第二位,所以加倍的是索引1,3,...,即原号从右向左的偶数位)。 - 具体:digits[1::2] 表示从索引1开始,每隔一个取一个,这些位置就是需要加倍的(即原号从右向左的偶数位)。 在引用[3]的C代码中: - 它没有反转字符串,而是从左向右处理(字符串a[0]是号最左边第一位)。 - 但是,它判断i%2==0(i从0开始)时,执行加倍操作。注意:这里i=0(第一位)被当作偶数位(因为0%2==0)进行加倍,而i=1(第二位)不加倍。这不符合Luhn算法从右向左计数的标准。 - 实际上,Luhn算法应该从右向左计数,所以我们需要知道号总位数。在C代码中,号固定16位,那么最右边第一位(校验位)应该是a[15](下标15)。那么,从右向左,第一位(索引15)不加倍,第二位(索引14)加倍,第三位(索引13)不加倍,第四位(索引12)加倍,以此类推。因此,在循环中,应该根据位置相对于最右边的距离来判断。但该代码直接使用下标i的奇偶性,这只有在号位数为偶数时,从左边开始计数会出错。 实际上,正确的做法是:从右向左,第1位(最右边)不加倍,第2位加倍,第3位不加倍,等等。所以,在循环中,我们可以通过计算位置(从右向左的位置)的奇偶性来决定是否加倍。或者,我们可以反转字符串,然后对反转后的字符串的偶数位(索引为奇数)进行加倍。 为了统一,我们采用以下方法(以16位号为例): - 设字符串为s,长度为16,索引0到15,其中s[15]是最右边第一位(校验位)。 - 从索引15开始向左,即i从15递减到0: - 位置序号(从右向左)为:15-i+1(这样i=15时,位置序号为1;i=14时,位置序号为2,等等)。 - 当位置序号是偶数时,则加倍(即位置序号2,4,6,...,16),对应i=14,12,10,...,0(即i为偶数时,位置序号为偶数?不对,因为位置序号=16-i,所以位置序号为偶数即16-i是偶数,也就是i是偶数?不对,16是偶数,所以16-i为偶数当且仅当i是偶数。所以,当i是偶数时,位置序号是偶数,需要加倍;i是奇数时,位置序号是奇数,不加倍?) - 但注意:在C代码中,循环是从左向右(i从0到15),那么我们需要判断的是:从右向左的位置序号=16-i(因为总长16)。那么: - i=0(最左边)时,位置序号=16(是偶数)->加倍 - i=1,位置序号=15(奇数)->不加倍 - ... - i=15,位置序号=1(奇数)->不加倍 - 所以,在C代码中,它用i%2==0来判断,实际上对于16位号,加倍的位置是:i=0,2,4,...,14(即偶数下标),不加倍的是i=1,3,5,...,15(奇数下标)。这符合Luhn算法吗? - 原号最左边第一位(i=0)在Luhn算法中应该是第16位(从右向左数第16位),因为16是偶数,所以加倍(正确)。 - 原号最右边第一位(i=15)是第1位(奇数位),不加倍(正确)。 - 所以,对于16位号,从左向右处理时,偶数下标(0-index)对应的是从右向左的偶数位(需要加倍),奇数下标对应的是从右向左的奇数位(不加倍)。因此,C代码中判断i%2==0来加倍是可行的,但仅限于号位数为偶数的情况。 如果号位数是奇数(例如15位),那么从左向右处理时,加倍规则会变化。例如,15位号: - 最左边第一位(i=0)在从右向左的位置是15(奇数)->不加倍。 - 最右边第一位(i=14)位置是1(奇数)->不加倍。 - 那么,加倍的位置应该是从右向左的偶数位,即位置2,4,...,14,对应i=13,11,...,1(即奇数下标,因为总位置15-i,当15-i为偶数时,i为奇数)。 因此,通用的做法是:从右向左处理(反转字符串),然后对反转后的字符串的偶数索引(即从右向左数的偶数位,在反转后的数组中索引为奇数)进行加倍。这样就不受总位数奇偶性的影响。 总结Luhn算法步骤(通用): 1. 移除所有非数字字符(如空格、连字符)。 2. 反转字符串。 3. 对反转后的字符串的每一位(索引从0开始): - 如果索引是奇数(即第2位、第4位等,对应原号从右向左的偶数位),则将该数字乘以2,如果结果大于等于10,则减去9(或等价地,将十位和个位相加)。 - 如果索引是偶数,则直接使用该数字。 4. 将处理后的所有数字相加,得到总和。 5. 如果总和能被10整除,则号有效;否则无效。 我们以用户提供的引用[2]的Python代码为例,它正是这样做的: 1. `sanitizedValue = idcard.replace("-", "")` 移除非数字字符。 2. `digits = [int(x) for x in reversed(sanitizedValue)]` 反转字符串并转为整数列表。 3. `even_digits = [d*2 for d in digits[1::2]]` 取反转后列表的奇数索引(即索引1,3,5,...)进行加倍。 4. 然后处理加倍后大于等于10的数字:`[d // 10 + d % 10 for d in even_digits]`(等价于如果d>=10则d-9,因为d最大为18,所以d//10是1,d%10是0~8,所以1+0~8=1~9,相当于d-9)。 5. 将加倍处理后的数字(even_digits)和未加倍的(digits[::2])分别求和,再相加。 6. 判断和是否能被10整除。 现在,我们可以用数学公式描述Luhn算法: 设信用号码为 $d_1 d_2 \cdots d_n$,其中 $d_n$ 是校验位(最右边一位)。定义: $$ S = \sum_{i=1}^{n} f(d_i, i) $$ 其中,函数 $f$ 定义为: $$ f(d_i, i) = \begin{cases} d_i & \text{if } (n-i+1) \text{ is odd} \\ 2 \cdot d_i & \text{if } (n-i+1) \text{ is even and } 2\cdot d_i < 10 \\ 2 \cdot d_i - 9 & \text{if } (n-i+1) \text{ is even and } 2\cdot d_i \geq 10 \end{cases} $$ 或者,等价地,我们也可以使用反转后的序列:令 $d_i' = d_{n+1-i}$(即反转序列),则: $$ S = \sum_{j=1}^{n} g(d_j', j) $$ 其中,$j$ 是反转后的位置($j=1$ 对应原序列的 $d_n$),函数 $g$ 定义为: $$ g(d_j', j) = \begin{cases} d_j' & \text{if } j \text{ is odd} \\ 2 \cdot d_j' & \text{if } j \text{ is even and } 2\cdot d_j' < 10 \\ 2 \cdot d_j' - 9 & \text{if } j \text{ is even and } 2\cdot d_j' \geq 10 \end{cases} $$ 然后,号有效当且仅当 $S \equiv 0 \pmod{10}$。 下面,我们给出一个通用的JavaScript实现(参考引用[1]并修正使其通用): ```javascript function luhnCheck(cardNumber) { // 移除非数字字符 let cleanCard = cardNumber.replace(/\D/g, ''); let len = cleanCard.length; let sum = 0; let shouldDouble = false; // 从最右边开始,第一位(校验位)不加倍,所以初始为false // 从最右边向左遍历(即从字符串末尾开始) for (let i = len - 1; i >= 0; i--) { let digit = parseInt(cleanCard.charAt(i), 10); if (shouldDouble) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; shouldDouble = !shouldDouble; // 切换标志,使下一位处理相反 } return (sum % 10 === 0); } ``` 或者,使用反转字符串的方法(与引用[2]类似): ```javascript function luhnCheck(cardNumber) { // 移除非数字字符 let cleanCard = cardNumber.replace(/\D/g, ''); // 反转字符串 let reversed = cleanCard.split('').reverse().join(''); let sum = 0; for (let i = 0; i < reversed.length; i++) { let digit = parseInt(reversed[i], 10); // 索引i为0,1,2,...,对应反转后的位置j(从1开始)为i+1 // 如果i是奇数(即j是偶数),则加倍 if (i % 2 === 1) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; } return (sum % 10 === 0); } ``` 注意:第二种方法中,反转后索引0对应原号最右边第一位(校验位),它应该不加倍(因为位置1是奇数),所以索引0(i为偶数0)不加倍,索引1(i为奇数1)加倍,以此类推。 我们也可以使用第一种方法(从右向左遍历,不反转字符串),通过一个标志位来切换是否加倍。 现在,回答用户的问题:信用验证算法(Luhn算法)如何验证信用号有效性? 回答结构: 1. 介绍Luhn算法的作用和原理。 2. 分步解释算法步骤。 3. 提供代码示例(选择一种实现)。 4. 注意事项(如移除非数字字符,算法不验证段和长度等)。 注意:Luhn算法只能验证号的数字组合是否有效(即校验和正确),但并不能验证该号是否真实存在、是否未过期、是否有足够余额等。此外,信用号的前几位(BIN)用于标识发机构,这些不在Luhn算法中验证,但通常在实际应用中会同时验证段和长度。 根据用户提供的引用,我们还可以看到引用[4]提到号开头数字(用于判断类型): - VISA以4开头 - MasterCard以5开头 - American Express以37开头 这些信息可以在通过Luhn算法验证后,进一步验证号所属的组织。 生成相关问题: 1. Luhn算法可以用于验证哪些类型的号码? 2. 信用号除了Luhn算法验证外,还有哪些验证步骤? 3. 如何用正则表达式验证信用号的段(BIN)?</think>信用号的有效性验证主要基于**Luhn算法**(又称模10算法),这是一种广泛使用的校验和公式。以下是算法的核心原理和实现步骤: --- ### **Luhn算法原理** 1. **从右向左处理数字** 从号最右侧(校验位)开始向左逐位处理,按位置分为两类: - **奇数位**(从右数第1、3、5...位):直接相加 - **偶数位**(从右数第2、4、6...位):先乘以2,若结果≥10则减去9(或拆分为个位+十位相加) 2. **求和与验证** 所有处理后的数字相加,若总和能被10整除,则号有效: $$ \left( \sum \text{处理后的数字} \right) \mod 10 = 0 $$ --- ### **代码实现示例(JavaScript)** ```javascript function isValidCreditCard(cardNumber) { // 移除非数字字符 const cleanCard = cardNumber.replace(/\D/g, ''); let sum = 0; let shouldDouble = false; // 控制是否加倍 // 从右向左遍历 for (let i = cleanCard.length - 1; i >= 0; i--) { let digit = parseInt(cleanCard.charAt(i), 10); if (shouldDouble) { digit *= 2; if (digit > 9) digit -= 9; // 等价于十位+个位 } sum += digit; shouldDouble = !shouldDouble; // 切换处理标志 } return sum % 10 === 0; } // 测试 console.log(isValidCreditCard("4111-1111-1111-1111")); // true (VISA测试) console.log(isValidCreditCard("1234-5678-9012-3456")); // false ``` --- ### **关键步骤解析** 1. **输入处理** 移除连字符/空格(如`4111-1111-1111-1111` → `4111111111111111`)[^1][^2][^3]。 2. **数字处理逻辑** - **偶数位**(如号右起第2位`1`)→ `1×2=2` → 直接加 - **偶数位**(如`6×2=12>9`)→ `12-9=3` 或 `1+2=3` - **奇数位**(如最右侧`1`)→ 直接加 3. **数学验证** 以测试号`4111111111111111`为例: ``` 原始:4 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 处理:8 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 (偶数位×2后调整) 求和:8+1+2+1+2+1+2+1+2+1+2+1+2+1+2+1 = 30 30 % 10 = 0 → 有效 ``` --- ### **注意事项** 1. **仅验证格式,不验证真实性** Luhn算法检查数字组合的合理性,但**无法验证号是否真实存在**或是否属于特定用户[^1][^4]。 2. **号长度与前缀** - 常见号长度:13~19位(VISA/MC通常16位,Amex为15位) - 前缀规则: - VISA → `4` - Mastercard → `51-55` - Amex → `34`或`37`[^4] 3. **非数字字符处理** 算法需先过滤非数字字符(如`-`或空格)[^2][^3]。 --- ### **其他语言实现** - **Python**:反转字符串后处理偶数索引位[^2] - **C语言**:直接遍历并分奇偶位置计算[^3] - **Java**:结合段前缀校验(如VISA以`4`开头)[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值