Generate Modulus 10 / Luhn Check Digit in PHP help?

Happy new year from Palo Alto! Please, I have been working for an inordinately long time trying to create some code for something that should be pretty basic. I have written some nice, simple PHP code that generates a random 11 digit alphanumeric codes, called $elevendigits, like this one:

FFM00976YH5

What I now need to do is write some code that generates a check digit according to the Modulus 10 - - sometimes called Luhn - - standard. Let’s call it it $checkdigit. It will be the twelfth digit.

I would have thought this an off-the-shelf function but, can’t seem to get anything to work. It would need to:

  1. Convert letters to numbers according to their ordinal place, starting with A=10, such that M=22, for example;
  2. Starting with the right-most digit, multiply every other digit by 2;
  3. Convert any two-digit products from the preceding step into 2 one-digit numbers;
  4. Add all the digits together; and then
  5. Subtract the sum from the next highest number ending in zero. This is the check digit.

Please, can anyone help me with the code necessary to accomplish this? I would have thought it easy, but am failing. Thank you sincerely!

It is a pretty simple method to write in PHP, as long as you understand the principle for how the Luhn Algorithm work, which it seems you do.

The difference with your project is that you also need to consider letters, and convert them as you hit any.

I am also not sure why you don’t reuse the method you created to make these numbers for the validation as well? You would just need to change it from setting the last digit, to validating it instead.

Give it another try and I’m sure your able to figure it out, remember modulus is your friend. If you run into problems, post your code here and we can take a look and help you out.

Found that via a google search, you could perhaps use that as a “blueprint” with each “step” being a seperate little section of code and wrap it up in a function.

Thank you, The Red Devil and Space Phoenix, for your encouragement. Here’s the code that I have so far:

<?
//generate a random string of 11 alphanumeric characters without vowels
function random_string()
{
    $character_set_array = array();
    $character_set_array[] = array('count' => 11, 'characters' => 'BCDFGHJKLMNPQRSTVWXYZ0123456789');
    $temp_array = array();
    foreach ($character_set_array as $character_set) {
        for ($i = 0; $i < $character_set['count']; $i++) {
            $temp_array[] = $character_set['characters'][rand(0, strlen($character_set['characters']) - 1)];
        }
    }
    shuffle($temp_array);
    return implode('', $temp_array);
}

$randomdigits=random_string();

//change all letters to numbers based on ordinal position and starting with B as eleven
$randomdigits = str_replace('B','11', $randomdigits);
$randomdigits = str_replace('C','12', $randomdigits);
$randomdigits = str_replace('D','13', $randomdigits);
$randomdigits = str_replace('F','15', $randomdigits);
$randomdigits = str_replace('G','16', $randomdigits);
$randomdigits = str_replace('H','17', $randomdigits);
$randomdigits = str_replace('J','19', $randomdigits);
$randomdigits = str_replace('K','20', $randomdigits);
$randomdigits = str_replace('L','21', $randomdigits);
$randomdigits = str_replace('M','22', $randomdigits);
$randomdigits = str_replace('N','23', $randomdigits);
$randomdigits = str_replace('P','25', $randomdigits);
$randomdigits = str_replace('Q','26', $randomdigits);
$randomdigits = str_replace('R','27', $randomdigits);
$randomdigits = str_replace('S','28', $randomdigits);
$randomdigits = str_replace('T','29', $randomdigits);
$randomdigits = str_replace('V','31', $randomdigits);
$randomdigits = str_replace('W','32', $randomdigits);
$randomdigits = str_replace('X','33', $randomdigits);
$randomdigits = str_replace('Y','34', $randomdigits);
$randomdigits = str_replace('Z','35', $randomdigits);
?>

So now, we wind up with $randomdigits as only numberals, which is great. But alas, that’s all I’ve been able to accomplish so far. I still need to multiply every other digit of $randomdigits by 2 starting with the right-most digit; convert any two-digit products from the preceding step into 2 one-digit numbers; add all the digits together; and finally
subtract the sum from the next highest number ending in zero to yield the check digit.

I am diligent, but stuck. Any help would be greatly appreciated! Thank you very much.

Hmm, ok. Yea, I can see why you might be a little stuck.

You are looking on this from perhaps the wrong perspective, and perhaps a little too procedural :slight_smile: If you have the time, I would recommend you to read up on OOP, it would greatly improve your development speed.

I have wrote a function for you that should work for both generating the validation number, as well as validating a complete number as well.

To use the different functions, you would change to the correct return method. One will give you the correct validation number that is missing, the other will tell you if the number is valid (bool).
When fetching the correct validation number it is important that you append a “x” (or any other number or character) to the end of the string. I.e. in your first post you mentioned “FFM00976YH5”, when passing this number along you would pass it as “FFM00976YH5x”, and then just replacing the x afterwards with the correct validation number.


function luhnAlgorithm($checkNumber) {
	$characters = array_flip(range('B', 'Z'));
	
	$length = strlen($checkNumber) - 1;
	$total_sum = 0;
	$cur_num = 0;
	
	for ($num=($length-1);$num >= 0;--$num) {
		if (!ctype_digit((string) $checkNumber[$num])) {
			$sum = $characters[$checkNumber[$num]] + 11;
			}
		else {
			$sum = $checkNumber[$num];
			}		
			
		if ($cur_num++ % 2 == 0) {
			$sum *= 2;
			}

		if ($sum > 9) {
			$sum = substr($sum, 0, 1) + substr($sum, 1, 1);
			}
			
		$total_sum += $sum;
		}

	return (10 - ($total_sum % 10));//Use this to return the missing validation number, note has to be passed with x in the end
	//return (($total_sum + $checkNumber[$length]) % 10 == 0); //Use this to validate the 12 digit number
	}

EDIT:
Forgot to mention that I have not tested the code, but it should work. In the event you have any problems, please let us know what is happening and we can help sort it out.

So, procedurally (Trying to wrap my head around the algorithm in 30 seconds)
For a given string

$string = "FFM00976YH5";

1: str_replace letters with numbers . This gives you a pure-digit string of variable length.

$string = str_replace(range('B','Z'),range('11','35'),$string)

2: Iterate to every other digit, starting from the right and walking back to the start.

for ($x = count($string)-1; $x >= 0; $x -= 2)

3: Double the value and Sum (via type juggling)

$sum += array_sum(str_split(($string[$x] * 2)));

4: Apply the math X*9 Mod 10;

$cd = ($sum * 9) % 10;

$cd is now my Check Digit.

Did i miss anything?

Point one would be wrong if you are looking for a true implementation of the algorithm, since you would treat B as 11 and not as 1 and 1 later on.

In your example the string “FFM00976YH5” would be treated as “1515220097634175” so the string goes from being 11 + Validation number to 16 + Validation number, and the length would vary depending on the number of characters in the string/number.

Point two is also not correct as you just illiterate over ever second number, while you also need to add every odd number to the sum as well (without doubling it).

Point three is correct, only thing is that you need the addition for the odd numbers which should not be doubled.

Point four, I am not sure why you are multiplying with 9 here?

On a side note if the validation is only for internal use, any way its implemented will work. The problem would be if you want it to work off other already established systems.

Or did i misinterpret your desire to convert the numbers (whole numbers instead of individual digits)? That would change the code to something along the lines of…

For a given string

$string = "FFM00976YH5";

1: Iterate to every other character, starting from the right and walking back to the start.

for ($x = count($string)-1; $x >= 0; $x -= 2) {

2: str_replace letters with numbers .

$char = str_replace(range('B','Z'),range('11','35'),$string[$x])

3: Double the value and Sum (via type juggling)

$sum += array_sum(str_split(($char * 2)));
}

4: Apply the math X*9 Mod 10;

$cd = ($sum * 9) % 10;

$cd is now my Check Digit.

(step 2 and 3 could be combined, but for clarity i left them seperate)

Altered in my second post (made while you were posting!)

In your example the string “FFM00976YH5” would be treated as “1515220097634175” so the string goes from being 11 + Validation number to 16 + Validation number, and the length would vary depending on the number of characters in the string/number.
hence “variable length”.

Point two is also not correct as you just illiterate over ever second number, while you also need to add every odd number to the sum as well (without doubling it).

Ah… yes, that will necessitate a little extra code… below.

Point four, I am not sure why you are multiplying with 9 here?

According to wikipedia, there are two methods of obtaining the value;
subtracting the modulo value from 10, or multiplying by 9 and taking the modulo of the result. Either way it’s 2 mathematical operations.

On a side note if the validation is only for internal use, any way its implemented will work. The problem would be if you want it to work off other already established systems.

Entirely agree.

Code modified:


$string = "FFM00976YH5";  
$string = strrev($string); //Turn the string around so that $x has meaning.
for ($x = 0; $x < strlen($string); $x++) { 
 $char = str_replace(range('B','Z'),range('11','35'),$string[$x]);  
 $sum += array_sum(str_split(($char * pow(2,(($x+1) % 2)))));  
}
 $cd = ($sum * 9) % 10;

I’m happy to report that I have a working cluster of code, thanks to the generous contributions of StarLion and The Red Devil. Part of the trouble I was having came from not recognizing that my host was running only PHP 4, which caused the str_split call to return undefined. But with encouragement from StarLion and The Red Devil, I plugged through and created a workaround using preg_split. Everything now works fine.

I am extremely grateful to StarLion and The Red Devil, without whom I would not have figured this out. Thank you!

Ask you host if they have the option of running php5 as php4 is no longer supported. If they don’t give some thought about moving hosts to one that does.

Support for PHP 4 has been discontinued since 2007-12-31. Please consider upgrading to PHP 5.
from: http://php.net/releases/index.php

The current stable release listed is 5.4.10