CS50 Credit作业Luhn校验码逻辑异常求助
CS50 2024 Credit作业Luhn校验问题分析
我在完成CS50 2024的Credit作业时,Luhn校验逻辑出现以下两个错误:
- 卡号4222222222222的校验和为40(本应有效),却被判定无效;
- 卡号4111111111111113的校验和为28(本应无效),却误判为VISA;
其余卡号识别正常。
以下是我的代码和问题分析:
最初的实现代码
#include <cs50.h> #include <ctype.h> #include <math.h> #include <stdio.h> bool digit(string str, int buffer); int numofdigits(long long card_num); int fst_ndigit(long long credit, int buffer, int numdigit); int main(void) { long long credit = get_long("please enter your credit card number : "); //Note: //using a boolean variable can reduce repetition of " INVALID " message bool valid = true; if (credit < (long long) pow(10, 13) || credit > (long long) pow(10, 16)) { printf("INVALID\n"); valid = false; } int buffer = numofdigits(credit); long long aux = credit; int last_two; int last_one; //Note: variable names cannot start with numbers! int secondlast_one; int mul = 1; int sum1 = 0; int sum2 = 0; if(valid){ for(int i = 0; aux > 0; i++) { //1st step : //Multiply every other digit by 2, starting with the number’s second-to-last digit, //and then add those products’ digits together. last_two = aux % (long long) pow(10, buffer-2); last_one = last_two % 10; secondlast_one = last_two / 10; mul = secondlast_one*2; if((mul / 10) != 0) { sum1 += (mul / 10) + (mul % 10); } else { sum1 += mul; } //2nd step: //Add the previous sum to the sum of the digits that weren’t multiplied by 2. sum2 += last_one; aux = aux / 100; } //3rd step: //If the total’s last digit is 0 //(or, put more formally, if the total modulo 10 is congruent to 0), //the number is valid! if((sum1 + sum2) % 10 ==0) { valid = true; } int fst_two = fst_ndigit(credit, buffer, 2); int fst_one = fst_ndigit(credit, buffer, 1); char str[17]; // Note : // sprintf(str, "%d", (int)credit); // this line is incorrect because the prog will spit out a random integer sprintf(str, "%lld", credit); // lld is format for long long int if (digit(str, buffer) && valid) { if (buffer == 15 && (fst_two == 34 || fst_two == 37)) { printf("AMEX\n"); } else if (buffer == 16 && (fst_two == 51 || fst_two == 52 || fst_two == 53 || fst_two == 54 || fst_two == 55)) { printf("MASTERCARD\n"); } else if ((buffer == 13 || buffer == 16) && fst_one == 4) { printf("VISA\n"); } else { printf("INVALID\n"); } } else { printf("INVALID\n"); } } } int numofdigits(long long card_num) { int count = 0; while (card_num > 0) { card_num /= 10; count++; } return count; } bool digit(string str, int buffer) { bool all_digits = true; for (int i = 0; i < buffer; i++) { if (!isdigit(str[i])) { all_digits = false; break; } } return all_digits; } int fst_ndigit(long long credit, int buffer, int numdigit) { int fstndigit = (int) (credit / (long long) pow(10, buffer - numdigit)); return fstndigit; }
尝试修改的代码片段
int buffer = numofdigits(credit); char str[17]; // Note : // sprintf(str, "%d", (int)credit); // this line is incorrect because the prog will spit out a random integer sprintf(str, "%lld", credit); // lld is format for long long int int sum = 0; int len = strlen(str); int i; // Iterate through the digits from right to left for (i = len - 1; i >= 0; i--) { int digit = str[i] - '0'; // Double every second digit if ((len - i) % 2 == 0) { digit *= 2; // Subtract 9 if the doubled digit is greater than 9 if (digit > 9) { digit -= 9; } } sum += digit; } int checksum = sum % 10; valid = (checksum == 0);
最终通过CS50检查的正确代码
#include <cs50.h> #include <ctype.h> #include <math.h> #include <stdio.h> bool digit(string str, int buffer); int numofdigits(long int credit); int fst_ndigit(long int credit, int buffer, int numdigit); bool checksum(long int credit); int main(void) { long int credit = get_long("please enter your credit card number : "); bool valid = true; if (credit < (long long) pow(10, 12) || credit > (long long) pow(10, 17)) { printf("INVALID\n"); valid = false; } if (valid) { valid = checksum(credit); if (valid) { int buffer = numofdigits(credit); int fst_two = fst_ndigit(credit, buffer, 2); int fst_one = fst_ndigit(credit, buffer, 1); if (buffer == 15 && (fst_two == 34 || fst_two == 37)) { printf("AMEX\n"); } else if (buffer == 16 && (fst_two == 51 || fst_two == 52 || fst_two == 53 || fst_two == 54 || fst_two == 55)) { printf("MASTERCARD\n"); } else if ((buffer == 13 || buffer == 16) && fst_one == 4) { printf("VISA\n"); } else { printf("INVALID\n"); } } else { printf("INVALID\n"); } } } bool checksum(long int credit) { int digit_count = 0; int sum = 0; // checksum for the luhn algorithm while (credit > 0) { int digit = credit % 10; credit /= 10; digit_count++; if (digit_count % 2 == 0) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; } return (sum % 10) == 0; } int numofdigits(long int credit) { int i = 0; while (credit > 0) { credit /= 10; i++; } return i; } bool digit(string str, int buffer) { bool all_digits = true; for (int i = 0; i < buffer; i++) { if (!isdigit(str[i])) { all_digits = false; break; } } return all_digits; } int fst_ndigit(long int credit, int buffer, int numdigit) { int fstndigit = (int) (credit / (long long) pow(10, buffer - numdigit)); return fstndigit; }
问题根源分析
初始代码Luhn算法实现逻辑完全错误
初始代码中计算last_two = aux % (long long) pow(10, buffer-2);的逻辑完全偏离了Luhn算法的要求:Luhn算法需要从卡号的最右侧(最后一位)开始向左遍历,每隔一位(从倒数第二位开始)翻倍。但这里用原始卡号的总位数buffer计算取余,导致每次获取的不是当前aux的最后两位,而是相对于原始卡号的固定偏移位,完全错误地选取了需要计算的数字,使得sum1和sum2的累加结果完全不符合校验要求。比如对于4222222222222,初始代码计算出的sum1+sum2根本不是40,因此判定无效;而4111111111111113的计算结果错误地满足了模10为0的条件,导致误判有效。valid变量处理存在漏洞
初始代码中仅在校验和符合条件时设置valid = true,但如果校验和不符合,valid会保持初始的true值(只有卡号长度超出范围时才会被设为false)。这导致即使校验和错误,只要卡号长度符合卡组织的要求,后续仍会进入卡类型判断逻辑,比如4111111111111113长度为16且首数字是4,因此被误判为VISA。最终代码的修正点
- 将Luhn校验逻辑封装为独立的
checksum函数,通过digit_count从右往左计数,正确识别需要翻倍的位(从右数第2、4、6...位),计算逻辑完全符合Luhn算法要求; - 修正了卡号长度的判断范围:从原来的13-16位改为12-17位,覆盖了VISA的13位卡号等合法长度;
valid变量的处理更严谨:校验和不通过时直接设为false,避免误判进入卡类型判断流程。
- 将Luhn校验逻辑封装为独立的
内容的提问来源于stack exchange,提问作者abeginner1
相关产品推荐
相关产品推荐

