C: Credit Card Number checker/ Luhn's algorithm

user1145538 picture user1145538 · Feb 8, 2012 · Viewed 8.6k times · Source

Everything looks fine and seems to follow Luhn's algorithm, but when i enter my own credit card number or this sample number that should be valid: 4388576018410707 , it still comes back as invalid...

Can anyone find the problem?

#include <stdio.h>

int isvalid(long num);
int sumofdoubleevenplace(long num);
int getdigit(int num);
int sumofoddplace(long num);
int prefixmatched(long num,int d);
int getsize(long d);
int getprefix(long num,int k);

main(){
  long cardnum=0;
  printf("Enter credit card number ");
  scanf("%ld",&cardnum);
  if(isvalid(cardnum)==1)
    printf("Valid card number\n");
else
    printf("Invalid card number\n ");
return 0;
}

int isvalid(long num){
if(((sumofoddplace(num)+sumofdoubleevenplace(num))%10==0)
   && (getsize(num)<=16 && getsize(num)>=13)
   && (prefixmatched(num,4)==1 || prefixmatched(num,5)==1 ||
       prefixmatched(num,6)==1 || prefixmatched(num,37==1)))
    return 1;
else
    return 0;
}

int sumofdoubleevenplace(long num){
int numdigits=getsize(num)-1;
int sum=0,i;
num/=10;
for(i=0;i<numdigits;i+=2){
    sum+=getdigit((int)(2*(num % 10)));
    num/=100;
}
return sum;
}

int getdigit(int num){
return ((num-num%10)/10)+num%10;
}

int sumofoddplace(long num){
int numberofdigits=getsize(num);
int sum=0,i;
for(i=0;i<numberofdigits;i+=2){
    sum+=num%10;
    num/=100;
}
return sum;
}

int prefixmatched(long num,int d){
if(getprefix(num,getsize(d))==d)
    return 1;
else
    return 0;
}

int getsize(long d){
int n=0;
while(d!=0){
    d/=10;
    n++;
}
return n;
}

int getprefix(long num,int k){
int numberofdigits=getsize(num);
int i;
if(numberofdigits-k>0){
    for(i=0;i<numberofdigits-k;i++){
        num/=10;
    }
    return num;
}
else
    return num;
}

Answer

Jonathan Leffler picture Jonathan Leffler · Feb 8, 2012

The first thing to do is to print the data you read; did you try that? With 64-byte integers, you can process 16-digit card numbers, though the input format has to be rather rigid. Alternatively, you can read the number as string (which means your program can allow optional punctuation; I find it very irksome that web sites do not allow you to type spaces or dashes where the digits are grouped on your actual credit card). When debugging problems, check that the input data the program is actually working with agrees with what you expect.

If you're using a 32-bit compilation, you have problems!

  • With your code compiled in 64-bit mode, the sample credit card number is identified as valid.

  • With your code compiled in 32-bit mode, the sample credit card number is identified as invalid. (There's a comment above that sizeof(long) == 4 on your machine with your compiler. Either you're compiling in 32-bit mode or you're compiling in 64-bit mode on a Windows 64-bit platform. See: What is the bit size of long on 64-bit Windows?)

  • When I used a Perl script I wrote a decade and more ago, the sample CCN is identified as valid.

  • When I printed out the value read by scanf() in the 32-bit mode (a modified version of your program), I got:

    $ ./ccn <<< 4388576018410707
    Enter credit card number Invalid card number -0000000089805613
    $
    

    That's using a bash-specific feature to feed the string (number) as standard input to the program.

Lessons to take away

  1. Know what your computer can handle in the way of numbers.

  2. Check the return value from scanf().

  3. However, even that does not help you with overflow. You would do better to read a line of text into a string and then use strtol() or a relative to check the conversion. With care, you can spot overflows and underflows as well as invalid values, etc. And you can report what the user typed back to them, whereas with a failed numeric conversion (or a bogus numeric conversion), you cannot report what the program saw.

  4. Print the input data. If you saw a negative number in the output when you typed in a positive one, you'd instantly know why there are problems.

  5. Were it my program, it would be processing command line arguments instead of prompting for input.


Perl example:

$ perl -MBRPS -e 'my($x, $y) = validate_account("4388-5760-1841-0708"); print "$x : $y\n";'
 : check digit on account number is incorrect
$ perl -MBRPS -e 'my($x, $y) = validate_account("4388-5760-1841-0707"); print "$x : $y\n";'
4388576018410707 : ok
$ perl -MBRPS -e 'my($x, $y) = validate_account("4388576018410707"); print "$x : $y\n";'
4388576018410707 : ok
$ perl -MBRPS -e 'my($x, $y) = validate_account("4388 5760 1841 0707"); print "$x : $y\n";'
4388576018410707 : ok
$ perl -MBRPS -e 'my($x, $y) = validate_account("4388 57601841 0707"); print "$x : $y\n";'
 : invalid punctuation pattern
$

This distinguishes between the presentation format, which is user-friendly and allows spaces or dashes to separate the groups of digits, and the internal operational format, the digit string that computers can handle. People should not be made to type 16-digit numbers without punctuation; that is downright uncivilized (for all that every web site I've encountered insists on no punctuation). You can write a function easily enough to format a 16-digit number with the breaks.

The function above is not intended to present data to the user - it does identify what the trouble is, but the programmer has to decide how to handle the errors.