You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题根源分析

  1. 初始代码Luhn算法实现逻辑完全错误
    初始代码中计算last_two = aux % (long long) pow(10, buffer-2);的逻辑完全偏离了Luhn算法的要求:Luhn算法需要从卡号的最右侧(最后一位)开始向左遍历,每隔一位(从倒数第二位开始)翻倍。但这里用原始卡号的总位数buffer计算取余,导致每次获取的不是当前aux的最后两位,而是相对于原始卡号的固定偏移位,完全错误地选取了需要计算的数字,使得sum1和sum2的累加结果完全不符合校验要求。比如对于4222222222222,初始代码计算出的sum1+sum2根本不是40,因此判定无效;而4111111111111113的计算结果错误地满足了模10为0的条件,导致误判有效。

  2. valid变量处理存在漏洞
    初始代码中仅在校验和符合条件时设置valid = true,但如果校验和不符合,valid会保持初始的true值(只有卡号长度超出范围时才会被设为false)。这导致即使校验和错误,只要卡号长度符合卡组织的要求,后续仍会进入卡类型判断逻辑,比如4111111111111113长度为16且首数字是4,因此被误判为VISA。

  3. 最终代码的修正点

    • 将Luhn校验逻辑封装为独立的checksum函数,通过digit_count从右往左计数,正确识别需要翻倍的位(从右数第2、4、6...位),计算逻辑完全符合Luhn算法要求;
    • 修正了卡号长度的判断范围:从原来的13-16位改为12-17位,覆盖了VISA的13位卡号等合法长度;
    • valid变量的处理更严谨:校验和不通过时直接设为false,避免误判进入卡类型判断流程。

内容的提问来源于stack exchange,提问作者abeginner1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 23:37:31