Home > Enterprise >  CS50 Problem Set 1 (Credit) 2022 Help Needed
CS50 Problem Set 1 (Credit) 2022 Help Needed

Time:11-29

I am trying to verify a credit card number by the steps below:

  1. Multiply every other digit by 2, starting with the number’s second-to-last digit, and then add those products’ digits together.

  2. Add the sum to the sum of the digits that weren’t multiplied by 2.

  3. Check if the last digit of the sum is 0.

Here is my code. I've tried an AMEX card number 371449635398431 but it's giving me INVALID so I believe my calculation is incorrect but I've ran over how it would work and I'm not sure what I'm doing wrong.

Thanks in advance for any help!

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

Ran the program and was provided INVALID even though a valid card number of 371449635398431 was used.

I believe my calculations are incorrect but I'm not sure where my error is. Any help would be greatly appreciated as I've looked at this repeatedly and cannot figure out where I'm going wrong.

CodePudding user response:

Your implementation of the Luhn algorithm is incorrect: the digits at even offsets from the end (counting from 0 for the last digit) should be doubled and if the result is greater than 9, the its digits should be added.

Here is a modified version of your code, using an array to implement the digit conversion:

#include <stdio.h>
#include <cs50.h>

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 val[10] = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
    int checksumpart1 = (val[second]   val[fourth]   val[sixth]   val[eighth]  
                         val[tenth]   val[twelth]   val[fourteenth]   val[sixteenth]);

    // Get every other other digit
    int first       = ((cardnumber / 1) % 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) {
        printf("INVALID\n");
    } else {
        printf("VALID\n");
    }
    return 0;
}

Here is a simpler version using a loop:

#include <stdio.h>
#include <cs50.h>

int main(void) {
    long cardnumber;
    do {
        cardnumber = get_long("Card no: ");
    }
    while (cardnumber < 0); // Card number must be greater than 0

    int val[] = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
    int sum = 0;
    for (long n = cardnumber; n; n /= 100) {
        sum  = n % 10   val[n / 10 % 10];
    }
    // Print AMEX or INVALID
    if (sum % 10)
        printf("INVALID\n");
    else
        printf("VALID\n");

    return 0;
}

Note that depending on the platform, type long may be too small to hold values greater than 231 (2147483647) thus unable to represent card numbers as long integers. It would be better to use a string to read the card number and check the digits:

#include <stdio.h>
#include <string.h>
#include <cs50.h>

int luhn_check(const char *str) {
    int val[10] = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
    int sum = 0;
    size_t len = strlen(str);
    for (size_t i = 0; i < len; i  ) {
        char c = str[len - i - 1];
        if (c >= '0' && c <= '9') {
            if (i % 2 == 0)
                sum  = c - '0';
            else
                sum  = val[c - '0'];
        } else {
            return 0;
        }
    }
    return sum % 10 == 0;
}

int main(void) {
    char *cardnumber = get_string("Card no: ");
    // Print AMEX or INVALID
    if (!luhn_check(cardnumber)) {
        printf("INVALID\n");
    } else {
        printf("VALID\n");
    }
    return 0;
}

CodePudding user response:

You have a problem here (at least):

    // 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);

you have to multiply each digit by 2, and if it is larger than 10, you have to add one to the result. When you add all the numbers together, you lose which numbers resulted in a number greater than, or equal to 10, so all those increments are lost (every digit in the result not in the units place is lost in the final remainde operation, but you need to consider the ones resulting from duplicating the digits)

NOTE: You don't need to convert the numbers to int integers to then separate the digits from the number. You can convert the individual digits from the string you receive as input (despite the type int has no enough capacity to hold a 20 digit card number, neither a long or unsigned long) but there's an automathon to parse the numbers in string form (processing the digits as they appear in the string)

The code below will compute the correctness of the luhn digits of a string of digits by using a finite automaton that maintains the remainder of the luhn algorithm as the automaton state, and imposes no limit to the input string (you can handle over 20 digit card number without problem):

#include <ctype.h>
#include <stdio.h>
#include <string.h>

const static char luhn_tab[][10] = {

    /* state 0, accepting state */
    { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 },
    { 11, 12, 13, 14, 15, 16, 17, 18, 19, 10 },
    { 12, 13, 14, 15, 16, 17, 18, 19, 10, 11 },
    { 13, 14, 15, 16, 17, 18, 19, 10, 11, 12 },
    { 14, 15, 16, 17, 18, 19, 10, 11, 12, 13 },
    { 15, 16, 17, 18, 19, 10, 11, 12, 13, 14 },
    { 16, 17, 18, 19, 10, 11, 12, 13, 14, 15 },
    { 17, 18, 19, 10, 11, 12, 13, 14, 15, 16 },
    { 18, 19, 10, 11, 12, 13, 14, 15, 16, 17 },
    { 19, 10, 11, 12, 13, 14, 15, 16, 17, 18 },

    /* state 10, accepting state */
    {  0,  2,  4,  6,  8,  1,  3,  5,  7,  9 },
    {  1,  3,  5,  7,  9,  2,  4,  6,  8,  0 },
    {  2,  4,  6,  8,  0,  3,  5,  7,  9,  1 },
    {  3,  5,  7,  9,  1,  4,  6,  8,  0,  2 },
    {  4,  6,  8,  0,  2,  5,  7,  9,  1,  3 },
    {  5,  7,  9,  1,  3,  6,  8,  0,  2,  4 },
    {  6,  8,  0,  2,  4,  7,  9,  1,  3,  5 },
    {  7,  9,  1,  3,  5,  8,  0,  2,  4,  6 },
    {  8,  0,  2,  4,  6,  9,  1,  3,  5,  7 },
    {  9,  1,  3,  5,  7,  0,  2,  4,  6,  8 },
};

const static int accepting = (1 << 0) | (1 << 10);

int luhn_ok(char *s, size_t s_len)
{
    s  = s_len; /* point to the string end */
    char st = 0; /* automaton state, initially zero */
    while (s_len--) {
        if (isdigit(*--s)) {
            /* operate only on digits, skipping nondigits */
            st = luhn_tab[st][*s - '0'];
        }
    }
    return ((1 << st) & accepting) != 0;
} /* luhn_ok */

The algorithm requires you to scan the string from the right side. If for some reason you cannot do that, and you need to parse it from the left, then you can use the full implementation I've published in GitHub. Feel free to use it, as it is opensource.

  • Related