Search code examples
phparraysmodulosubtractionfmod

PHP modulo vs substract at PHP_INT_MAX


At some point i had this block of code:

while( $i> $l-1 )
{
    $x= fmod($i,$l);
    $i= floor($i/$l);
}

I decided to get rid of the modulo operation and wrote this block:

while( true )
{
    $d= floor( $i/$l );
    if( $d>= 1 )
    {
        $x= $i - ($d*$l);
        $i= $d;
    }
    else
    {
        break;
    }
}

The $x is used for indexing an array of length $l. The $i is in question here.

While for some relatively small initial $i, both blocks give the same $x over all iterations, when initialized with something close to PHP_INT_MAX the two blocks do not give the same $x.

Unfortunately $l cannot become a power of 2 in order to use bit operators so i am stuck with this.

I am guessing it has something to do with the inner roundings that take place. Could fmod be so optimized for this case? Is there something i am not seeing?

Additional Comment after accepting @trincot 's answer.

One thing i should have mentioned is that although one would expect the second method to produce better results, due to using simple subtraction, it did not. Possibly because of the division taking place at the beginning of the loop.(that is why i asked "Could fmod be so optimized).


Solution

  • According to the documentation, fmod works on floats:

    fmod — Returns the floating point remainder (modulo) of the division of the arguments

    Instead, the modulo operator (%) would be more suitable for what you need:

    Operands of modulus are converted to integers (by stripping the decimal part) before processing.

    fmod will become inaccurate for large integers as the floating point representation does not have the same precision.

    Examples of some oddities that happen:

    $l=3;
    $i=9223372036854775295;
    echo is_int($i) . "<br>"; // 1 (true)
    echo (9223372036854775295==$i) . "<br>"; // 1 (true)
    echo number_format($i, 0, ".", "") . "<br>"; // 9223372036854774784
    echo fmod($i,$l) . "<br>";   // 1
    echo fmod($i-1,$l) . "<br>"; // 1
    echo fmod($i-2,$l) . "<br>"; // 1
    echo ($i % $l) . "<br>";     // 2
    echo (($i-1) % $l) . "<br>"; // 1
    echo (($i-2) % $l) . "<br>"; // 0
    

    Notice how a simple number_format already destroys the precision of the integer; it returns a different number because of floating point conversion.

    Notice also that this lack of precision makes fmod return 1 for three consecutive numbers, while the modulo operator does what you would want.

    So you seem much better of with %.

    Alternative

    Your function seems to break down a number into its "digits" in an L-basis. For instance, when $l=2, your $x-sequence produces the binary representation of the number, except for the last digit which you leave out.

    In that respect, you might have a look at the function call base_convert($i,10,$l), which produces one digit corresponding to a value of $x in your code, with letters for digits above 9. The function can accept $l values up to 36.