Luhn alghorithm

hi!

I am making an application here but I am stuck with a checksum my customer requires for encoding cards they use for door access.

They uses the Luhn 10 checksum, I found two  functions for this, but both returns the wrong values, maybe one of you experts know how to change this so I get the correct value...

  function LuhnCheckDigit(const AInputString: string): Byte;
  var
    i: Integer;
    LSum: Integer;
    LDigit: Integer;
  begin
    i := 0;
    LSum := 0;
    while i < Length(AInputString) do
    begin
      LDigit := StrToInt(AInputString[Length(AInputString) -  i]);
      LDigit := LDigit * (1 + (i mod 2));
      LSum := LSum + (LDigit div 10) + (LDigit mod 10);
      inc(i);
    end;
    Result := LSum mod 10;
  end;


function Mod10(const Value: string): string;
var
  i, intOdd, intEven: Integer;
  MyResult : integer;
begin
  {add all odd seq numbers}
  intOdd := 0;
  i := 1;
  while (i <= Length(Value)) do
  begin
    Inc(intOdd, StrToIntDef(Value[i], 0));
    Inc(i, 2);
  end;

  {add all even seq numbers}
  intEven := 0;
  i := 2;
  while (i <= Length(Value)) do
  begin
    Inc(intEven, StrToIntDef(Value[i], 0));
    Inc(i, 2);
  end;

  MyResult := 3*intOdd + intEven;
  {modulus by 10 to get}
  MyResult := MyResult mod 10;
  if MyResult <> 0 then
    MyResult := 10 - MyResult;

    Result := IntToStr(MyResult)
end;


Example values:
Result should be 1 : 91843862918438629
Result should be 7 : 270093000000000    
Result should be 6 : 310023000000000
Result should be 7 : 000078000000000



LVL 2
joepeztAsked:
Who is Participating?

[Webinar] Streamline your web hosting managementRegister Today

x
 
epasquierConnect With a Mentor Commented:
got it
function CalcLUHN(S:String):Char;
Var
 i,F,V,Sum:integer;
begin
 F:=2;
 Sum:=0;
 for i:=Length(S) downto 1 do
  begin
   if Not (S[i] In ['0'..'9']) Then Exit;
   V:=F*(Ord(S[i])-Ord('0'));
   if V>9 Then V:=(V Mod 10)+1;
   Sum:=Sum+V;
   F:=3-F;
  end;
 Sum:=Sum Mod 10;
 if Sum<>0 Then Sum:=10-Sum;
 Result:=Chr(Sum+$30);
end;

Open in new window

0
 
epasquierCommented:
here is a function that checks that the LUHN key (last digit) is correct. I tested it with you values, and return true with
918438629184386291
2700930000000007

from this, 2 options
1) I state it just for fun, try all digits from '0'..'9' added to your key and return the one that return true with this function... Ok, I'm not proud of this idea :o)))
2) reverse engineer this function to correct your own algo

I'll give it more time to think about option 2
function CheckLUHN(S:String):Boolean;
Var
 i,F,V,Sum:integer;
begin
 Result:=False;
 F:=1;
 Sum:=0;
 for i:=Length(S) downto 1 do
  begin
   if Not (S[i] In ['0'..'9']) Then Exit;
   V:=F*(Ord(S[i])-Ord('0'));
   if V>9 Then V:=(V Mod 10)+1;
   Sum:=Sum+V;
   F:=3-F;
  end;
 Result:=(Sum Mod 10)=0;
end;

Open in new window

0
 
joepeztAuthor Commented:
just an add, they key is not actual added i think, maybe I misunderstood this but I will take a test when I get home.. the point here is that the last digit should be the same as the returning digit frmo the function ?
0
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

 
sgvillCommented:
The algorithm's that you gave assume you have the check digit in the string of numbers and you are checking if it is valid (if it is, it should return a 0).   What epasquier has done is given you a way to create the check digit.   Your original algorithms would work (as his) with minor changes.  For instance, in the first one:  change
              LDigit := LDigit * (1 + (i mod 2));     to
              LDigit := LDigit * (i mod 2);
And then add the following line before end
              if Result <> 0 Then  Result:= 10 - Result;
(which is the same as epasquier did with his algorithm :) )

 
0
 
epasquierCommented:
@sgvill:
nope, I think this is correct (appart other error as stated below) :
LDigit := LDigit * (1 + (i mod 2));

i mod 2 can return either 0 or 1, but the algo need to multiply once by 1, once by 2

I haven't checked his algo before, and not entirely even now, but I think his problem was coming from the fact that the LUHN algo started the multiplier (2 or 1) from the END of the string, not from the begining. so :

    i := 0;
    LSum := 0;
    while i < Length(AInputString) do
    begin
...
     inc(i);
    end;

is incorrect, it should be :

i:= Length(AInputString)-1
while i>=0 do
begin
 ...
// and not use (i mod 2) to jump from 1 or 2 as multiplier, but do something similar of what I did
// which is fix the first value, and switch at each digit using the fact that 3-(1 or 2) will give you (2 or 1)
 dec(i);
end;
0
 
8080_DiverCommented:
Try the following function.  I have set it up so that it will return a boolean so that you can use it in an if statement more easily.
function ValidLUHN(const EntryToValidate : String):Char;
var
  TempNdx     : Integer;
  TempFactor  : Integer;
  TempCalc    : Integer
  TempSum     : Integer;
  TempResult  : Boolean;
begin
  TempFactor  :=  2;
  TempSum     :=  0;
  TempResult  :=  False;
  try
    for TempNdx  :=  Length(EntryToValidate) downto 1 do
    begin
      case EntryToValidate[TempNdx] do
        '0'..'9': begin
                    TempCalc    :=  TempFactor  *
                                  (Ord(EntryToValidate[TempNdx])  - Ord('0'));
                    if (TempCalc > 9)
                    then begin
                      TempCalc  :=  (TempCalc Mod 10)+1;
                    end; {if}
                    TempSum     :=  TempSum + TempCalc;
                    TempFactor  :=  3 - TempFactor;
                  end
        else  begin
                raise Exception.Create( 'ERROR: Invalid entry; ' + #13#10 +
                                        'Nonnumeric characters are not allowed.');
              end;
      end; {case}
    end;
    TempResult  :=  ((TempSum Mod 10) = 0);
  finally
    Result  :=  TempResult;
  end; {try}
end;

Open in new window

0
 
sgvillCommented:
@epasquier
oops! you are correct, my mod is wrong.

His algorithm (which is on Wikipedia, by the way, under Luhn) does go backwards.  The problem is that the alternating doubling starts with the second number from the end ASSUMING the last number is the checksum.  So, if you are trying to generate the checksum, you need to start alternating with the last number.

So, the modified mod line, i believe, should be

LDigit := LDigit * (1 + ((i+1) mod 2));
0
 
8080_DiverCommented:
Finally got a chance to test the function and it didn't work.  However, the following one does work.
function ValidLUHN(const EntryToValidate: String): Boolean;
var
  TempStr     : string;
  TempNdx     : Integer;
  TempFactor  : Integer;
  TempCalc    : Integer;
  TempSum     : Integer;
  TempResult  : Boolean;
begin
  TempFactor  :=  1;
  TempNdx     :=  Length(EntryToValidate);
  TempSum     :=  0;
  TempStr     :=  ReverseString(EntryToValidate);
  TempResult  :=  False;
  try
    for TempNdx  :=  1 to Length(EntryToValidate) do
    begin
      case EntryToValidate[TempNdx] of
        '0'..'9': begin
                    TempCalc    :=  TempFactor  *
                                  (Ord(TempStr[TempNdx])  - Ord('0'));
                    if (TempCalc > 9)
                    then begin
                      TempCalc  :=  (TempCalc Mod 10)+1;
                    end; {if}
                    TempSum     :=  TempSum + TempCalc;
                    TempFactor  :=  3 - TempFactor;
                  end
        else  begin
                raise Exception.Create( 'ERROR: Invalid character [' +
                                        TempStr[TempNdx] + '] in entry; ' + #13#10 +
                                        'Nonnumeric characters are not allowed.');
              end;
      end; {case}
    end;
    TempResult  :=  ((TempSum Mod 10) = 0);
  finally
    Result  :=  TempResult;
  end; {try}
end;

Open in new window

0
 
epasquierCommented:
that's a strange idea to first reverse the string where it could be as simple to scan the string from last to first instead of first to last...
Assuming ones know that a for loop can use downto instead of to
for i:=0 to 5 do ...for i:=5 downto 0 do ...

and a minor error : you check EntryToValidate[TempNdx] in your case and use TempStr[TempNdx] in your calc, which is reversed

apart from that, your algo is exactly the same as my first CheckLUHN function (only diff : you raise an error if you encounter an invalid character where I return false), and it's not the question if you read correctly, the problem is CALCULATING the key
0
 
8080_DiverCommented:
epasquier,
Assuming ones know that a for loop can use downto instead of to
Now, that was a polite slap. ;-)
 Yes, I do know about the downto capabilities; however, I have also found that it can cause just the slightest of mental pauses when someone else maintains my code where I have used that.  (It seems that most people tend to think in a forward dirction rather than in a backwards direction. ;-)  So, as with the more explanatory variable naming, I code for maintenance as well as immediate gratification. ;-)
You are correct on the "minor error".  That was introduced during a slight modification to allow comparisons between what came in and what was being checked.
 
0
 
epasquierCommented:
>> Now, that was a polite slap. ;-)

Sorry , didn't slept too much these past few days, I'm a bit itchy.

I intended to tell everyone who listen that it exists, not just you. As you said, most developers don't know a CPU can run in reverse mode without sweating more. That's all the more evident with CLI/STI ASM operands that can produce VERY effective results combined with STO/LOD operators in REP/LOOP mode on some algorithm (does it sound nerd ?).

About the maintenance aspect, I prefer give the occasion of some newcomer to learn something rather to stick with basic methods. But I understand and agree that this point of view have its limits.

I'm sure you didn't take it personally ;o)
0
 
8080_DiverCommented:
epasquier,
If I took something like that personally, I would have had to abandon my career several decades ago.  ;-)
I prefer give the occasion of some newcomer to learn something rather to stick with basic methods.
However, if you use goog coding practices in the process, then the newbies will tend to unconsciously learn those as well as the finer points being illustrated.  Also, IMHO, by using more human-consumable naming, it is easier for the newbie to follow.
 
0
 
epasquierCommented:
> by using more human-consumable naming, it is easier for the newbie to follow.
if by that you refer to the i (standard for index), F (Factor) ,V (Value) variables, I agree that could have been better even if not too difficult to follow seeing the compact code. Thing is, I had this code already for a project, I didn't wrote it specifically for this question. Otherwise I might have done a little effort. On the other end, you might have overkill that necessity with adding Temp in every of your local variable names.

Anyway, it has been a real pleasure to talk about these little difference in coding habits with you.
I appreciate very much the fact that you challenge the point of view, that's very refreshing and valuable in the long run for every one who reads it (even if off-topic)
0
 
joepeztAuthor Commented:
Super :) thanks :)

now I can finally continue my proejct:)
0
All Courses

From novice to tech pro — start learning today.