Search code examples
phpapachemamplamp

Weird behaviour in PHP and Apache2: different output in different servers


I'm experiencing different outputs in PHP code running in Mac and Linux.

I have 2 servers running the following code:

    $ltt = ((ord($str[7]) << 24) | (ord($str[8]) << 16) | (ord($str[9]) << 8) | (ord($str[10]))) / 1000000;

Even the ord(str[ ]) outputs are the same:

[7] = 254
[8] = 26
[9] = 22 
[10] = 216

But, on the MAMP stack (Mac) running php 5.3.6, if $ltt is originally supposed to be a negative number, it returns 4263.12265 (incorrect).

On the LAMP stack (Ubuntu) running same php version, it will return the exact negative value -31.84465.

This happens only with negative numbers..

Update Addl. Info:

  • A var dump gives þØçï_Kstring(25) "þØçï_K"
  • bin2hex gives 000e1b00000000fe1a16d806e707ef0000045f0000004b0000

Simplying the function to include only numeric inputs, the output still differs:

$ltt = (254 << 24 | 26 << 16 |  22 << 8 | 216)/ 1000000;

4263.12265 on MAMP and -31.84465 on LAMP


Solution

  • This is a 32 vs 64 bit problem.

    Because your most significant byte is > 127, on a 32 bit platform this is interpreted as a negative value because of integer overflow - the most significant bit is set. On a 64-bit platform it is not.

    The solution is to use pack() and unpack() so you can specify that the integer should be signed. EDIT Fixed this code sample See edit 2

    $packed = pack('C*', ord($str[7]), ord($str[8]), ord($str[9]), ord($str[10]));
    $unpacked = unpack('l', $packed);
    $lat = current($unpacked);
    

    ...however you should also be conscious that this will not work on a little-endian architecture, because the byte ordering will be wrong. You can simply reverse the order of the packed bytes to work around this, but I am just trying to wrap my head around a works-everywhere solution.

    EDIT 2

    OK, it took me a while to wrap my head around this but we got there in the end:

    What you need to do is, if the most significant bit is set, OR the result with a number where the least significant 32 bits are not set but the rest are. So the following works on both 32 and 64 bit:

    <?php
    
    // The input bytes as ints
    $bytes = array(254, 26, 22, 216);
    
    // The operand to OR with at the end
    $op = $bytes[0] & 0x80 ? (~0 << 16) << 16 : 0;
    
    // Do the bitwise thang
    $lat = (($bytes[0] << 24) | ($bytes[1] << 16) | ($bytes[2] << 8) | $bytes[3]) | $op;
    
    // Convert to float for latitude
    $lat /= 1000000;
    
    echo $lat;