В курсе cs50 (Гарвард) есть задача по программированию на Си по выявлению правильности номера кредитной карты через алгоритм Луна (credit.c). Разберем эту задачку и покажем два варианта еще решения.
Итак. На любой кредитке напечатан номер, который также хранится в базе данных банка, выдавшего карту. Номера кредиток генерируются таким образом, что у них есть некоторая «контрольная сумма» — математическое соотношение между по крайней мере одним числом и другими. Эта контрольная сумма позволяет быстро провести первоначальную проверку и обнаружить опечатки на стороне клиента без необходимости делать запрос к серверу.
Тут-то нам и помогает Алгоритм Луна (создан Гансом Питером Луном из IBM), который позволяет проверить валидность карты таким образом:
- Начиная с конца (с предпоследней цифры), умножьте каждую вторую цифру на 2, а затем сложите цифры этих частей вместе;
- Прибавьте сумму к сумме цифр, которые не умножались на 2;
- Если последняя цифра суммы равна 0 (т.е. сумма по модулю 10 равна 0) — карта валидна.
Возьмем номер карты VISA и выделим каждую вторую цифру, начиная с предпоследней цифры:
4003600000000014
Мы получим цифры:
4 0 6 0 0 0 0 1
Напишем эти цифры наоборот, для удобства:
1 0 0 0 0 6 0 4
Умножим каждую из этих цифр на 2:
1*2 + 0*2 + 0*2 + 0*2 + 0*2 + 6*2 + 0*2 + 4*2
Получаем:
2 + 0 + 0 + 0 + 0 + 12 + 0 + 8
Сложим цифры этих частей (т. е. не сами части) вместе:
2 + 0 + 0 + 0 + 0 + 1 + 2 + 0 + 8 = 13
Теперь добавим эту сумму (13) к сумме цифр, которые не умножались на 2 (начиная с конца):
13 + 4 + 0 + 0 + 0 + 0 + 0 + 3 + 0 = 20
Ура! Последняя цифра полученного числа равна 0 — карта валидна. Теперь давайте напишем программу, которая дает возможность пользователю ввести номер карточки и узнать — валидна ли она; а также к какому типу карт она принадлежит.
Чтобы понять тип карты: American Express использует 15-значные номера, MasterCard использует 16-значные номера, а Visa использует 13- и 16-значные номера. Все номера American Express начинаются с 34 или 37; большинство номеров MasterCard начинаются с 51, 52, 53, 54 или 55 (у них также есть некоторые другие потенциальные начальные номера); все номера Visa начинаются с 4.
Эта задачка может быть довольно сложной для новичков, поэтому если у вас что-то не вышло, дам несколько советов для начала; ну а решения буду опубликованы ниже. Аккуратно, спойлеры 🙂
- во-первых, подумайте, как вы будете сохранять номер кредитки. Он довольно большой. Класть его сразу в массив или строку мы не будем, а положим его в длинную-длинную переменную (спойлер: long long int; чтобы увидеть — выдели).
- далее.. если непонятно с какой стороны подходить к задаче: скопируйте (или распечатайте) пример с карточкой 4003600000000014. Держите его все время перед глазами и просто выполняйте то, что там написано 🙂
- для начала для наглядности начните писать программу с помощью обычных переменных, а уже потом спойлер: переведите все на массивы.
- Взять тестовые номера кредиток для тестирования программы можно у палки.
Решение Игроглаза. Я работаю непосредственно в своей IDE, поэтому не использую неродную библиотеку cs50. Это сказывается на том, что ввод приходится фильтровать ручками. У Штукенции в решении эта библиотека используется. Для начала мое первое решение, которое прошло у меня все тесты, но было забраковано тестировщиком cs50:
#include <stdio.h>
#include <limits.h>
/*
4003600000000014 x16
. . . . . . . .
400360000000001 x15
. . . . . . .
4003600000000 x13
. . . . . .
*/
int main(void)
{
long long int cc = 0; // cc number; could be 13, 15, 16 numbers
int first_multiply = 0;
int second_summ = 0;
int final_summ = 0;
printf("Enter your CC number:\n");
do
{
if (!scanf("%lld", &cc) || // scanf returns 1 if valid
(cc < (long long)100000000000 && !(cc > LLONG_MAX))) // min/max CC number
printf("INVALID\n");
while (getchar() != '\n') ; // clear buffer
}
while (cc < (long long)100000000000 && !(cc > LLONG_MAX)); // min/max CC number
// now get separate numbers (2nd starting from end)
// make array which contain 9 elements (we will use only 8)
int n[9]; for (int i = 0; i < 10; i++) n[i] = 0; // fill array with 0;
n[1] = (cc % 100) / 10;
n[2] = (cc % 10000) / 1000;
n[3] = (cc % 1000000) / 100000;
n[4] = (cc % 100000000) / 10000000;
n[5] = (cc % 10000000000) / 1000000000;
n[6] = (cc % 1000000000000) / 100000000000;
n[7] = (cc % 100000000000000) / 10000000000000;
n[8] = (cc % 10000000000000000) / 1000000000000000;
// multiplied x2 (`d` - doubled)
int d[9]; for (int i = 0; i < 10; i++) d[i] = 0; // fill array with 0;
d[1] = (n[1]*2);
d[2] = (n[2]*2);
d[3] = (n[3]*2);
d[4] = (n[4]*2);
d[5] = (n[5]*2);
d[6] = (n[6]*2);
d[7] = (n[7]*2);
d[8] = (n[8]*2);
// Extract number from 0 to 9 from doubled numbers with module (`m`).
// Sometimes we will have 1 digit, sometimes 2 (then one will be 1).
int m[9]; for (int i = 0; i < 10; i++) m[i] = 0; // fill array with 0;
for (int i = 1; i < 9; i++) // must lurk only at 8 (legit array)
{
if (d[i] > 10)
m[i] = (d[i] % 10) + 1; // as we need summ, we can add 1 right on
else if (d[i] == 10)
m[i] = 1;
else
m[i] = d[i];
}
// first half done:
first_multiply = m[1]+m[2]+m[3]+m[4]+m[5]+m[6]+m[7]+m[8];
// now take leftover digits from initial CC number
int x[9]; for (int i = 0; i < 10; i++) x[i] = 0; // fill array with 0;
x[1] = (cc % 10);
x[2] = (cc % 1000) / 100;
x[3] = (cc % 100000) / 10000;
x[4] = (cc % 10000000) / 1000000;
x[5] = (cc % 1000000000) / 100000000;
x[6] = (cc % 100000000000) / 10000000000;
x[7] = (cc % 10000000000000) / 1000000000000;
x[8] = (cc % 1000000000000000) / 100000000000000;
// done! now calculate:
second_summ = x[1]+x[2]+x[3]+x[4]+x[5]+x[6]+x[7]+x[8];
final_summ = first_multiply + second_summ;
if (final_summ % 10 == 0)
{
if (cc < (long long)10000000000000) // 13 digits
{
printf("VISA\n");
}
else if (cc < (long long)1000000000000000) // 15 digits
{
printf("AMEX\n");
}
else if (cc < (long long)10000000000000000) // 16 digits
{
if (n[8] == 4) // visa cc always starts with 4
printf("VISA\n");
else
printf("MCARD\n");
}
}
else
printf("Credit card INVALID\n");
getchar();
getchar();
}
А вот мое второе решение, которое все тесты на cs50 прошло успешно:
#include <stdio.h>
#include <limits.h>
/*
4003600000000014 x16
. . . . . . . .
400360000000001 x15
. . . . . . .
4003600000000 x13
. . . . . .
*/
int main(void)
{
long long int cc = 0; // cc number; could be 13, 15, 16 numbers
int first_multiply = 0;
int second_summ = 0;
int final_summ = 0;
printf("Enter your CC number:\n");
do
{
scanf("%lld", &cc);
while (getchar() != '\n') ; // clear buffer
}
while (cc < (long long)100000000);
if (cc < (long long)1000000000000 || cc >= (long long)10000000000000000) // min/max CC number: error reporting
{
printf("INVALID\n");
return 0;
}
// now get separate numbers (2nd starting from end)
// make array which contain 9 elements (we will use only 8)
int n[9];
for (int i = 0; i < 10; i++)
{
n[i] = 0; // fill array with 0;
}
n[1] = (cc % 100) / 10;
n[2] = (cc % 10000) / 1000;
n[3] = (cc % 1000000) / 100000;
n[4] = (cc % 100000000) / 10000000;
n[5] = (cc % 10000000000) / 1000000000;
n[6] = (cc % 1000000000000) / 100000000000;
n[7] = (cc % 100000000000000) / 10000000000000;
n[8] = (cc % 10000000000000000) / 1000000000000000;
// multiplied x2 (`d` - doubled)
int d[9];
for (int i = 0; i < 10; i++)
{
d[i] = 0; // fill array with 0;
}
d[1] = (n[1] * 2);
d[2] = (n[2] * 2);
d[3] = (n[3] * 2);
d[4] = (n[4] * 2);
d[5] = (n[5] * 2);
d[6] = (n[6] * 2);
d[7] = (n[7] * 2);
d[8] = (n[8] * 2);
// Extract number from 0 to 9 from doubled numbers with module (`m`).
// Sometimes we will have 1 digit, sometimes 2 (then one will be 1).
int m[9];
for (int i = 0; i < 10; i++)
{
m[i] = 0; // fill array with 0;
}
for (int i = 1; i < 9; i++) // must lurk only at 8 (legit array)
{
if (d[i] > 10)
{
m[i] = (d[i] % 10) + 1; // as we need summ, we can add 1 right on
}
else if (d[i] == 10)
{
m[i] = 1;
}
else
{
m[i] = d[i];
}
}
// first half done:
first_multiply = m[1] + m[2] + m[3] + m[4] + m[5] + m[6] + m[7] + m[8];
// now take leftover digits from initial CC number
int x[9];
for (int i = 0; i < 10; i++)
{
x[i] = 0; // fill array with 0;
}
x[1] = (cc % 10);
x[2] = (cc % 1000) / 100;
x[3] = (cc % 100000) / 10000;
x[4] = (cc % 10000000) / 1000000;
x[5] = (cc % 1000000000) / 100000000;
x[6] = (cc % 100000000000) / 10000000000;
x[7] = (cc % 10000000000000) / 1000000000000;
x[8] = (cc % 1000000000000000) / 100000000000000;
// done! now calculate:
second_summ = x[1] + x[2] + x[3] + x[4] + x[5] + x[6] + x[7] + x[8];
final_summ = first_multiply + second_summ;
if (final_summ % 10 == 0)
{
if (cc < (long long)10000000000000) // 13 digits
{
printf("VISA\n");
}
else if (cc < (long long)1000000000000000 && // 15 digits
((cc >= (long long)340000000000000 && cc < (long long)350000000000000) || // starts with 34
(cc >= (long long)370000000000000 && cc < (long long)380000000000000))) // starts with 37
{
printf("AMEX\n");
}
else if (cc < (long long)10000000000000000) // 16 digits
{
if (n[8] == 4) // visa cc always starts with 4
{
printf("VISA\n");
}
else if (cc >= (long long)5100000000000000 && cc < (long long)5600000000000000) // MCARD starts with 51,52,53,54,55
{
printf("MASTERCARD\n");
}
else
{
printf("INVALID\n");
}
}
else
{
printf("INVALID\n");
}
}
else
{
printf("INVALID\n");
}
return 0;
}
Решение №3 (читерское от Штукенции).
Привет, это Штукенция. Поняв, что задача сложная, я полностью потеряла веру в себя и прибегла к подсказкам из интернета. Но Игроглазу сказала, что все решение сделала сама, так как мы сами проходим курс без подсказок.
Получилось вот что:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string cardtype="";
long long int cardnum = get_long("Card number:");
int i=0;
long long int lenth = cardnum;
long long int lenthcardname = cardnum;
long long int lenthcardcheck = cardnum;
// Check lenth of input cardnumber (VALID or NOT):
for (i=0; lenth>0; i++)
{
lenth=lenth/10;
}
printf("Lenth:%i\n",i);
if (i<13 || i>16)
{
cardtype="INVALID\n";
}
else
{
cardtype="OK, LENTH\n";
}
printf("Lenth check:%s\n",cardtype);
// Check card type (AMEX, VISA, MASTERCARD):
long long int firstnumcheck=lenthcardcheck;
if (i==15)
{
printf("AMEX\n");
}
else if (i==13)
{
printf("VISA\n");
}
else if (i==16)
{
firstnumcheck=firstnumcheck/1000000000000000;
if (firstnumcheck==4)
printf("VISA\n");
else
printf("MASTERCARD\n");
}
printf("%lld\n",firstnumcheck);
// Check Luhn algoruthm (VALID or NOT):
int numbers1;
int numbers2;
int firstdig;
int seconddig;
int sumnum1=0;
int sumnum2=0;
do
{
numbers1=cardnum%10;
cardnum=cardnum/10;
sumnum1+=numbers1;
numbers2=cardnum%10;
cardnum=cardnum/10;
numbers2=numbers2*2;
firstdig=numbers2/10;
seconddig=numbers2%10;
sumnum2+=firstdig+seconddig;
}
while(cardnum>0);
int sumnum;
sumnum=sumnum1+sumnum2;
if (sumnum%10==0)
{
printf("GOOD! VALID CARD\n");
}
else
printf("INVALID\n");
}
Потом пришлось во всем сознаться и переделывать с нуля. Более подробно об этой ситуации:
https://youtu.be/fM9Bnow_nTA
Решение Штукенции честное-кошачье:
После предыдущего фейла, взяв себя в руки, удалось решить задачу самостоятельно с использованием модуля.
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string cardtype="";
long long int cardnum = get_long("Card number:");
int i=0;
long long int lenth = cardnum;
long long int lenthcardname = cardnum;
long long int lenthcardcheck = cardnum;
// Check the Luhn Algorithm
int num1 = cardnum%10;
int num2 = ((cardnum%100)/10)*2;
int num3 = (cardnum%1000)/100;
int num4 = ((cardnum%10000)/1000)*2;
int num5 = (cardnum%100000)/10000;
int num6 = ((cardnum%1000000)/100000)*2;
int num7 = (cardnum%10000000)/1000000;
int num8 = ((cardnum%100000000)/10000000)*2;
int num9 = (cardnum%1000000000)/100000000;
int num10 = ((cardnum%10000000000)/1000000000)*2;
int num11 = (cardnum%100000000000)/10000000000;
int num12 = ((cardnum%1000000000000)/100000000000)*2;
int num13 = (cardnum%10000000000000)/1000000000000;
int num14 = ((cardnum%100000000000000)/10000000000000)*2;
int num15 = (cardnum%1000000000000000)/100000000000000;
int num16 = ((cardnum%10000000000000000)/1000000000000000)*2;
int num2dig1 = num2%10;
int num4dig1 = num4%10;
int num6dig1 = num6%10;
int num8dig1 = num8%10;
int num10dig1 = num10%10;
int num12dig1 = num12%10;
int num14dig1 = num14%10;
int num16dig1 = num16%10;
int sumnumdig1 = num2dig1+num4dig1+num6dig1+num8dig1+num10dig1+num12dig1+num14dig1+num16dig1;
int num2dig2 = (num2-num2dig1)/10;
int num4dig2 = (num4-num4dig1)/10;
int num6dig2 = (num6-num6dig1)/10;
int num8dig2 = (num8-num8dig1)/10;
int num10dig2 = (num10-num10dig1)/10;
int num12dig2 = (num12-num12dig1)/10;
int num14dig2 = (num14-num14dig1)/10;
int num16dig2 = (num16-num16dig1)/10;
int sumnumdig2 = num2dig2+num4dig2+num6dig2+num8dig2+num10dig2+num12dig2+num14dig2+num16dig2;
int sumnum1=sumnumdig1+sumnumdig2;
int sumnum2=num1+num3+num5+num7+num9+num11+num13+num15;
int sumnum=sumnum1+sumnum2;
printf("Sumnum= %d\n",sumnum);
// Check if card valid: lenth of cardnumber & AMEX, VISA, MASTERCARD:
long long int firstnumcheck=lenthcardcheck;
for (i=0; lenth>0; i++)
{
lenth=lenth/10;
}
printf("Lenth:%i\n",i);
if (i<13 || i>16)
{
cardtype="INVALID\n";
}
else
{
cardtype="OK, LENTH\n";
if (sumnum%10==0)
{
if (i==15)
{
printf("AMEX\n");
}
else if (i==13)
{
printf("VISA\n");
}
else if (i==16)
{
firstnumcheck=firstnumcheck/1000000000000000;
if (firstnumcheck==4)
printf("VISA\n");
else
printf("MASTERCARD\n");
}
printf("%lld\n",firstnumcheck);
printf("GOOD! VALID CARD\n");
}
else
printf("INVALID\n");
}
printf("Lenth check:%s\n",cardtype);
}
И код этой программы после тестирования багов выглядит так:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
//Check typy of creditcard and if it's even valid
int main(void)
{
string cardtype = "INVALID\n";
long long int cardnum = get_long("Card number:");
int i = 0;
long long int lenth = cardnum;
long long int lenthcardname = cardnum;
long long int lenthcardcheck = cardnum;
// Check the Luhn Algorithm
// First use module to identify each number of card
int num1 = cardnum % 10;
int num2 = ((cardnum % 100) / 10) * 2;
int num3 = (cardnum % 1000) / 100;
int num4 = ((cardnum % 10000) / 1000) * 2;
int num5 = (cardnum % 100000) / 10000;
int num6 = ((cardnum % 1000000) / 100000) * 2;
int num7 = (cardnum % 10000000) / 1000000;
int num8 = ((cardnum % 100000000) / 10000000) * 2;
int num9 = (cardnum % 1000000000) / 100000000;
int num10 = ((cardnum % 10000000000) / 1000000000) * 2;
int num11 = (cardnum % 100000000000) / 10000000000;
int num12 = ((cardnum % 1000000000000) / 100000000000) * 2;
int num13 = (cardnum % 10000000000000) / 1000000000000;
int num14 = ((cardnum % 100000000000000) / 10000000000000) * 2;
int num15 = (cardnum % 1000000000000000) / 100000000000000;
int num16 = ((cardnum % 10000000000000000) / 1000000000000000) * 2;
int num2dig1 = num2 % 10;
int num4dig1 = num4 % 10;
int num6dig1 = num6 % 10;
int num8dig1 = num8 % 10;
int num10dig1 = num10 % 10;
int num12dig1 = num12 % 10;
int num14dig1 = num14 % 10;
int num16dig1 = num16 % 10;
// Find first sum of digits
int sumnumdig1 = num2dig1 + num4dig1 + num6dig1 + num8dig1 + num10dig1 + num12dig1 + num14dig1 + num16dig1;
// Find second sum of digits
int num2dig2 = (num2 - num2dig1) / 10;
int num4dig2 = (num4 - num4dig1) / 10;
int num6dig2 = (num6 - num6dig1) / 10;
int num8dig2 = (num8 - num8dig1) / 10;
int num10dig2 = (num10 - num10dig1) / 10;
int num12dig2 = (num12 - num12dig1) / 10;
int num14dig2 = (num14 - num14dig1) / 10;
int num16dig2 = (num16 - num16dig1) / 10;
int sumnumdig2 = num2dig2 + num4dig2 + num6dig2 + num8dig2 + num10dig2 + num12dig2 + num14dig2 + num16dig2;
int sumnum1 = sumnumdig1 + sumnumdig2;
int sumnum2 = num1 + num3 + num5 + num7 + num9 + num11 + num13 + num15;
int sumnum = sumnum1 + sumnum2; // Final sum of digits
// Check if card valid: lenth of cardnumber & AMEX, VISA, MASTERCARD:
long long int firstnumcheck = lenthcardcheck;
for (i = 0; lenth > 0; i++)
{
lenth = lenth / 10;
}
if (i < 13 || i > 16) // If cardlenth invalid
{
printf("INVALID\n");
}
else
{
cardtype = "OK, LENTH\n";
if (sumnum % 10 == 0) // Check if the last digit of final sum = 0
{
int amex_check = firstnumcheck / 10000000000000;
if (i == 15 && (amex_check == 34 || amex_check == 37)) // Check if it's AmericanExpress
{
printf("AMEX\n");
}
else if (i == 13) // Check if it's VISA by lenth
{
printf("VISA\n");
}
else if (i == 16) // Check if it's VISA or Mastercard by first numbers
{
int firstnumcheck_16 = firstnumcheck / 1000000000000000;
int secondnumcheck = firstnumcheck / 100000000000000;
if (firstnumcheck_16 == 4)
{
printf("VISA\n");
}
else if (secondnumcheck == 51 || secondnumcheck == 52 || secondnumcheck == 53 || secondnumcheck == 54 || secondnumcheck == 55)
{
printf("MASTERCARD\n");
}
else
{
printf("INVALID\n");
}
}
else
{
printf("INVALID\n");
}
}
else
{
printf("INVALID\n");
}
}
}
