CS50 2022 Problem Set 1 (Credit) 信用卡校验代码错误求助
信用卡号Luhn校验代码错误排查
我尝试通过以下步骤验证信用卡号:
- 从卡号倒数第二位开始,每隔一位数字乘2,再将这些乘积的各位数字相加。
- 将上述结果与未乘2的各位数字之和相加。
- 检查总和的最后一位是否为0。
但编写的C代码在测试有效AMEX卡号371449635398431时返回INVALID,代码如下:
int main(void) { // TODO Prompt for input (DONE) long cardnumber; do { cardnumber = get_long("Card no: "); } while (cardnumber < 0); // Card number must be greater than 0 // TODO Calculate checksum // Get every other digit int second = ((cardnumber / 10) % 10); int fourth = ((cardnumber / 1000) % 10); int sixth = ((cardnumber / 100000) % 10); int eighth = ((cardnumber / 10000000) % 10); int tenth = ((cardnumber / 1000000000) % 10); int twelth = ((cardnumber / 100000000000) % 10); int fourteenth = ((cardnumber / 10000000000000) % 10); int sixteenth = ((cardnumber / 1000000000000000) % 10); // Multiply every other digit by 2 then add those digits int checksumpart1 = (second * 2 + fourth * 2 + sixth * 2 + eighth * 2 + tenth * 2 + twelth * 2 + fourteenth * 2 + sixteenth * 2); // Get every other other digit int first = (cardnumber % 10); int third = ((cardnumber / 100) % 10); int fifth = ((cardnumber / 10000) % 10); int seventh = ((cardnumber / 1000000) % 10); int ninth = ((cardnumber / 100000000) % 10); int eleventh = ((cardnumber / 10000000000) % 10); int thirteenth = ((cardnumber / 1000000000000) % 10); int fifteenth = ((cardnumber / 100000000000000) % 10); // Sum of every other digit int checksumpart2 = (first + third + fifth + seventh + ninth + eleventh + thirteenth + fifteenth); // Validity check addition of first two sums int checksumfinal = ((checksumpart1 + checksumpart2) % 10); // Print AMEX or INVALID // validate checksum if (checksumfinal != 0) { printf("INVALID\n"); } else { printf("VALID\n"); } }
错误分析
核心逻辑错误:未拆分乘积的各位数字相加
计算checksumpart1时,直接将乘2后的数字相加,不符合Luhn算法要求。比如数字7乘2得14,应该将1+4=5加入总和,而非直接加14。卡号长度适配问题
AMEX卡号为15位,但代码固定按16位提取数字,导致sixteenth等高位变量为0,虽不是当前测试失败的主因,但会影响其他长度卡号的校验。
修正方案
方案1:修复当前代码的计算逻辑
新增一个辅助函数拆分乘积的各位数字,修改checksumpart1的计算方式:
// 新增辅助函数:计算数字的各位和 int sum_digits(int n) { return (n / 10) + (n % 10); } // 替换原checksumpart1的计算代码 int checksumpart1 = sum_digits(second * 2) + sum_digits(fourth * 2) + sum_digits(sixth * 2) + sum_digits(eighth * 2) + sum_digits(tenth * 2) + sum_digits(twelth * 2) + sum_digits(fourteenth * 2) + sum_digits(sixteenth * 2);
方案2:改用循环处理任意长度卡号(更健壮)
固定提取位数的方式扩展性差,建议用循环遍历卡号每一位,自动适配不同长度:
int main(void) { long cardnumber; do { cardnumber = get_long("Card no: "); } while (cardnumber < 0); long temp = cardnumber; int checksumpart1 = 0; int checksumpart2 = 0; int count = 0; while (temp > 0) { int digit = temp % 10; count++; // 倒数第1、3、5...位(未乘2的位) if (count % 2 == 1) { checksumpart2 += digit; } // 倒数第2、4、6...位(需乘2后拆位相加) else { int doubled = digit * 2; checksumpart1 += (doubled / 10) + (doubled % 10); } temp /= 10; } int checksumfinal = (checksumpart1 + checksumpart2) % 10; if (checksumfinal != 0) { printf("INVALID\n"); } else { // 可在此补充卡号开头数字和长度校验,区分AMEX/VISA/MASTERCARD等 printf("VALID\n"); } }
测试验证
用卡号371449635398431测试修正后的代码,会正确返回VALID。
内容的提问来源于stack exchange,提问作者DKXP
相关产品推荐
相关产品推荐

