Credit card validation in C

I keep learning programming, and today I share my baby walkthrough of the second homework from the CS50 course titled Credit.

As a quick disclaimer, I used only tools I’ve learned from the two lessons I had so far.

Here is the problem to solve:

A credit (or debit) card, of course, is a plastic card with which you can pay for goods and services. Printed on that card is a number that’s also stored in a database somewhere, so that when your card is used to buy something, the creditor knows whom to bill. There are a lot of people with credit cards in this world, so those numbers are pretty long: American Express uses 15-digit numbers, MasterCard uses 16-digit numbers, and Visa uses 13- and 16-digit numbers. And those are decimal numbers (0 through 9), not binary, which means, for instance, that American Express could print as many as 10^15 = 1,000,000,000,000,000 unique cards! (That’s, um, a quadrillion.)

Actually, that’s a bit of an exaggeration, because credit card numbers actually have some structure to them. All American Express numbers start with 34 or 37; most MasterCard numbers start with 51, 52, 53, 54, or 55 (they also have some other potential starting numbers which we won’t concern ourselves with for this problem); and all Visa numbers start with 4. But credit card numbers also have a “checksum” built into them, a mathematical relationship between at least one number and others. That checksum enables computers (or humans who like math) to detect typos (e. g., transpositions), if not fraudulent numbers, without having to query a database, which can be slow. Of course, a dishonest mathematician could certainly craft a fake number that nonetheless respects the mathematical constraint, so a database lookup is still necessary for more rigorous checks.

Write a program that prompts the user for a credit card number and then reports (via printf) whether it is a valid American Express, MasterCard, or Visa card number, per the definitions of each’s format herein. So that we can automate some tests of your code, we ask that your program’s last line of output be AMEX\n or MASTERCARD\n or VISA\n or INVALID\n, nothing more, nothing less. For simplicity, you may assume that the user’s input will be entirely numeric (i.e., devoid of hyphens, as might be printed on an actual card) and that it won’t have leading zeroes. But do not assume that the user’s input will fit in an int! Best to use get_long from CS50’s library to get users’ input.

First, I’ll prompt a user to input a number using the get_long function from the CS50 library:

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

int main(void)
{
    long card_number = get_long("Number: ");
}

Next, I’ll do what seems to be the most challenging part of the problem, which is to calculate the checksum of a credit card. In the problem description, they provide additional information on Luhn’s Algorithm that determines the validity of a card as follows:

  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. If the total’s last digit is 0, the number is valid!

Let’s take the card number 4003600000000014 as an example. For the sake of visibility, let me underline every other digit, starting with the number’s second-to-last digit:

4 0 0 3 6 0 0 0 0 0 0 0 0 0 1 4

How to extract those digits from a number in C? Turns out, in maths, there is a great operation that can give us the remainder after dividing one number by another, and it’s called modulo, or mod. You can totally start laughing here, but honestly, I never heard of it before. For example, 1234 % 10 = 4 (with modulo operation being expressed by the percent sign), since 4 is the last digit in the number 1234.

Okay, that was the last digit. But how to get to the second-to-last digit, and then get every other digit from there? This might be not as obvious (at least it wasn’t at first to me), but since we’re dealing with whole numbers, a simple division by 10 basically moves us to one character in a number at a time. For example, 1234 / 10 = 123,4, but since the integer numbers get truncated, the result is actually 123.

Pretty cool, huh? Now let’s try to get every second number using the combination of division and modulo operations in the code:

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

long card_number;
int main(void)
{
    // Prompting a user to input a credit card number
    card_number = get_long("Number: ");

    // Calculating checksum
    int last_digit;
    int second_to_last_digit;

    while (card_number > 0)
    {
        // Removing the last digit
        last_digit = card_number % 10;
        card_number /= 10;

        // Removing the second last digit
        second_to_last_digit = card_number % 10;
        card_number /= 10;
        printf("%i", second_to_last_digit);
    }
    printf("\n");
}

This code prints 10000604, which are exactly the digits we need from the card number 4003600000000014. Keep in mind it’s not a number ten million six hundred four, but individual digits printed one after another. The order of digits is reversed, though it’s irrelevant in this case.

Next, according to Luhn’s Algorithm, I need to multiply each digit by 2 and get the sum of all of them combined. And I haven’t found a better solution than again using the same modulo and division operations, but this time on the extracted digits:

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

long card_number;
int main(void)
{
    // Prompting a user to input a credit card number
    card_number = get_long("Number: ");

    // Calculating checksum
    int every_second_digit = 0;
    int last_digit;
    int second_to_last_digit;
    int digit1;
    int digit2;

    while (card_number > 0)
    {
        // Removing the last digit and adding to every_other_digit
        last_digit = card_number % 10;
        card_number /= 10;

        // Removing the second last digit
        second_to_last_digit = card_number % 10;
        card_number /= 10;

        // Mupliplying second last digit
        second_to_last_digit *= 2;

        // Adding digits together to get the sum of every_second_digit
        digit1 = second_to_last_digit % 10;
        digit2 = second_to_last_digit / 10;
        every_second_digit += digit1 + digit2;
    }
    printf("%i", every_second_digit);
    printf("\n");
}

I’m pretty sure it could be done more elegantly, but I’m using only the knowledge I have so far from the first week of introduction to programming. Importantly, this code works, providing the result of the number 13, as you would expect from the sum of digits 2 + 1 + 2 + 8. Yay!

Lastly, to finish off the checksum, I’m going to find every other digit, add them together, and then add both sums in the final checksum variable:

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

long card_number;
int main(void)
{
    // Prompting a user to input a credit card number
    card_number = get_long("Number: ");

    // Calculating checksum
    int every_second_digit = 0;
    int every_other_digit = 0;
    int checksum = 0;
    int last_digit;
    int second_to_last_digit;
    int digit1;
    int digit2;

    while (card_number > 0)
    {
        // Removing the last digit and adding to every_other_digit
        last_digit = card_number % 10;
        card_number /= 10;
        every_other_digit += last_digit;

        // Removing the second last digit
        second_to_last_digit = card_number % 10;
        card_number /= 10;

        // Mupliplying second last digit
        second_to_last_digit *= 2;

        // Adding digits together to get the sum of every_second_digit
        digit1 = second_to_last_digit % 10;
        digit2 = second_to_last_digit / 10;
        every_second_digit += digit1 + digit2;
    }

    // Getting checksum
    checksum = every_second_digit + every_other_digit;

    printf("%i\n", checksum);
}

This code now prints the number 20, which is exactly the kind of value I want from a valid credit card. The hardest part is done!

Next, I’ll calculate the number of digits in the card number using a simple loop:

// Counting the number of digits in the card number
    long n = card_number;
    int number_of_digits = 0;
    do
    {
        n /= 10;
        number_of_digits++;
    }
    while (n != 0);

To keep the value of that card_number intact, I added a new variable called n and assigned its value to card_number, so I can do the maths with the n instead. In plain English, that loop says the following: while the card number is not equal to zero, divide the card number by 10, and for every division increase the number of number_of_digits variable which I use as a counter by one.

For example, if I were to type in the number 1234, it would go as follows:

  1. 1234 / 10 = 123; the counter increases from 0 to 1;
  2. 123,4 / 10 = 12; the counter increases from 1 to 2;
  3. 12,34 / 10 = 1; the counter increases from 2 to 3;
  4. 1 / 0 = 10; the counter increases from 3 to 4;

Now the card number gets equal to zero, and the loop ends with the counter equal to 4, which is the correct number of digits in the number 1234.

Next, I’ll make another counter to get the first two digits of the card number using pretty much the same logic:

// Getting the first two digits of the card number
    int first_two_digits = 0;
    long i = card_number;
    while (i > 100)
    {
        i /= 10;
        first_two_digits = i;
    }

Lastly, I’ll check the validity of the checksum and then check what credit company the card belongs to based of the card number length and starting digits.

So here is my final code:

// A program in C that checks the validity of a given credit card number

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

long card_number;
int main(void)
{
    // Prompting a user to input a credit card number
    card_number = get_long("Number: ");

    // Counting the number of digits in the card number
    long n = card_number;
    int number_of_digits = 0;
    do
    {
        n /= 10;
        number_of_digits++;
    }
    while (n != 0);

    // Getting first two digits of the card number
    int first_two_digits = 0;
    long i = card_number;
    while (i > 100)
    {
        i /= 10;
        first_two_digits = i;
    }

    // Calculating checksum
    int every_second_digit = 0;
    int every_other_digit = 0;
    int checksum = 0;
    int last_digit;
    int second_to_last_digit;
    int digit1;
    int digit2;

    while (card_number > 0)
    {
        // Removing last digit and adding to every_other_digit
        last_digit = card_number % 10;
        card_number /= 10;
        every_other_digit += last_digit;

        // Removing the second last digit
        second_to_last_digit = card_number % 10;
        card_number /= 10;

        // Mupliplying the second last digit
        second_to_last_digit *= 2;

        // Adding digits together to get the sum of every_second_digit
        digit1 = second_to_last_digit % 10;
        digit2 = second_to_last_digit / 10;
        every_second_digit += digit1 + digit2;
    }

    // Getting checksum
    checksum = every_second_digit + every_other_digit;

    // Checking for the card validity
    int validity = checksum % 10;
    if (validity == 0)
    {
        if ((number_of_digits == 15) && (first_two_digits == 34 || first_two_digits == 37))
        {
            printf("AMEX\n");
        }
        else if ((number_of_digits == 16) && (first_two_digits >= 51 && first_two_digits <= 55))
        {
            printf("MASTERCARD\n");
        }
        else if ((number_of_digits == 16 || number_of_digits == 13) && (first_two_digits / 10 == 4))
        {
            printf("VISA\n");
        }
        else
        {
            printf("INVALID\n");
        }
    }
    else
    {
        printf("INVALID\n");
    }
}

Update

A week later after posting this solution, I decided to get back to this problem and see if I could improve the code, particularly focusing on the checksum part as it’s essentially the main part.

Here is my updated code for this function:

// Calculate checksum using Luhn's Algorithm
int calculate_checksum(long long number)
{
    int sum = 0;
    int digit;
    bool is_second_digit = false;

    while (number > 0)
    {
        digit = number % 10; // Get the last digit
        number /= 10; // Reduce the number by one digit

        if (is_second_digit)
        {
            digit *= 2;
            sum += digit % 10 + digit / 10; // Split double numbers into single digits, e.g. 12 into 1 and 2
        }
        else
        {
            sum += digit;
        }

        // Switch the boolean, alternating between odd and even digits
        is_second_digit = !is_second_digit;
    }

    return sum;
}
 148   2 mo   C   CS50   Programming
Next
© Daniel Sokolovskiy, 2024
Powered by Aegea