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?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Emmanuel PASQUIERFreelance Project ManagerCommented:
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
Emmanuel PASQUIERFreelance Project ManagerCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

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
Emmanuel PASQUIERFreelance Project ManagerCommented:
@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
Emmanuel PASQUIERFreelance Project ManagerCommented:
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
Emmanuel PASQUIERFreelance Project ManagerCommented:
>> 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
Emmanuel PASQUIERFreelance Project ManagerCommented:
> 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
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.