Question

I have a class for computing the Luhn checksum for a number. It takes integer as an input and returns true or false to indicate validity or otherwise, or it throws an exception if an inappropriate data type is given as input.

The code is as follows (The full source is on GitHub):

class Luhn extends abstr\Prop implements iface\Prop
{
    /**
     * Test that the given data passes a Luhn check. 
     * 
     * @return bool True if the data passes the Luhn check
     * @throws \InvalidArgumentException 
     * @see http://en.wikipedia.org/wiki/Luhn_algorithm
     */
    public function isValid ()
    {
        $data   = $this -> getData ();
        $valid  = false;

        switch (gettype ($data))
        {
            case 'NULL'     :
                $valid  = true;
            break;
            case 'integer'  :
                // Get the sequence of digits that make up the number under test
                $digits = array_reverse (array_map ('intval', str_split ((string) $data)));
                // Walk the array, doubling the value of every second digit
                for ($i = 0, $count = count ($digits); $i < $count; $i++)
                {
                    if ($i % 2)
                    {
                        // Double the digit
                        if (($digits [$i] *= 2) > 9)
                        {
                            // Handle the case where the doubled digit is over 9
                            $digits [$i]    -= 10;
                            $digits []      = 1;
                        }
                    }
                }
                // The Luhn is valid if the sum of the digits ends in a 0
                $valid  = ((array_sum ($digits) % 10) === 0);
            break;
            default         :
                // An attempt was made to apply the check to an invalid data type
                throw new \InvalidArgumentException (__CLASS__ . ': This property cannot be applied to data of type ' . gettype ($data));
            break;
        }

        return ($valid);
    }
}

I also built a full unit test to exercise the class.

My main development environment is a workstation running 64 bit builds PHP 5.3 and Apache under OSX Lion. I also use a laptop running a 64 bit build of Apache and PHP 5.4 also under Apache. As well as this I have a Ubuntu Linux virtual machine running 64 bit Apache and PHP 5.3. The unit test was fine for all of these, as expected.

I thought I could some spare time during lunch at work (Windows 7, XAMPP, 32 bit PHP 5.3) for working on the project that this class is a part of, but the first thing I ran into was failure of the unit test.

The problem is that on a 32 bit build of PHP the number gets silently cast to float if it exceeds the limits of a 32 bit integer. My proposed solution is to have a special case for float. If the input type is float, and its value is outside the range that can be expressed in int (PHP_INT_MIN .. PHP_INT_MAX) then I'll number_format() it to get it back into a string of digits. If it's within the range of an integer then I'll throw an exception.

However, this leads to its own problem. I know that the further away you get from 0 with a floating point number, the less resolution the number has (the smaller the increment between a given number and the next representable number gets). How far away from 0 do you have to get before it becomes impossible to represent the integer part of the number before you can't reliably represent the integer part any more? (I'm not sure if that's really clear, so for example, say the limit is 1000 before the resolution drops below the difference between one int and the next. I could enter a digit bigger than 1000, say 1001, but the limitations of floating point numbers means it ends up being 1001.9 and rounding it yields 1002, meaning I've lost the value I was interested in).

Is it possible to detect when the loss in resolution will become an issue for a floating point number?

EDIT TO ADD: I suppose I could modify the extension to accept a string instead of a numeric type and then verify that it contains only digits with a regex or some other similar technique, but as Luhn-checkable data is a string of digits that doesn't feel right to me, somehow. There are extensions for PHP that can handle bignums, but as they're extensions and this is meant to be a piece of framework code that could potentially be deployed over a wide range of configurations, I'd rather not rely on the presence of such extensions if at all possible. Besides, none of the above addresses the issue that if you give PHP a big int it silently converts it to float. I need a way of detecting that this has happened.

Was it helpful?

Solution

If you need precision, you should not use floats.

Instead, especially as you want to work with integers (if I understand correctly), you could try working with the gmp* functions: GMP - GNU Multiple Precision

If you cannot work with that extension you might get some additional ideas from

OTHER TIPS

If you need precision, you should not use floats.

Instead, especially as you want to work with integers (if I understand correctly), you could try working with the bc* functions : BCMath Arbitrary Precision Mathematics

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top