phplong-integerliteralspackdouble-precision

64-bit Float Literals PHP


I'm trying to convert a 64-bit float to a 64-bit integer (and back) in php. I need to preserve the bytes, so I'm using the pack and unpack functions. The functionality I'm looking for is basically Java's Double.doubleToLongBits() method. http://docs.oracle.com/javase/7/docs/api/java/lang/Double.html#doubleToLongBits(double)

I managed to get this far with some help from the comments on the php docs for pack():

function encode($int) {
        $int = round($int);

        $left = 0xffffffff00000000;
        $right = 0x00000000ffffffff;

        $l = ($int & $left) >>32;
        $r = $int & $right;

        return unpack('d', pack('NN', $l, $r))[1];
}
function decode($float) {
        $set = unpack('N2', pack('d', $float));
        return $set[1] << 32 | $set[2];
}

And this works well, for the most part...

echo decode(encode(10000000000000));

100000000

echo encode(10000000000000);

1.1710299640683E-305

But here's where it gets tricky...

echo decode(1.1710299640683E-305);

-6629571225977708544

I have no idea what's wrong here. Try it for yourself: http://pastebin.com/zWKC97Z7

You'll need 64-bit PHP on linux. This site seems to emulate that setup: http://www.compileonline.com/execute_php_online.php


Solution

  • It is working properly, the only problem in this case is in logic of:

    echo decode(1.1710299640683E-305);
    

    You can't use "rounded" and "human readable" output of echo function to decode the original value (because you are loosing precision of this double then).

    If you will save the return of encode(10000000000000) to the variable and then try to decode it again it will works properly (you can use echo on 10000000000000 without loosing precision).

    Please see the example below which you can execute on PHP compiler as well:

    <?php
        function encode($int) {
            $int = round($int);
    
            $left = 0xffffffff00000000;
            $right = 0x00000000ffffffff;
    
            $l = ($int & $left) >>32;
            $r = $int & $right;
    
            return unpack('d', pack('NN', $l, $r))[1];
        }
    
        function decode($float) {
            $set = unpack('N2', pack('d', $float));
            return $set[1] << 32 | $set[2];
        }
    
        echo decode(encode(10000000000000)); // untouched
        echo '<br /><br />';
    
        $encoded = encode(10000000000000);
        echo $encoded; // LOOSING PRECISION! 
        echo ' - "human readable" version of encoded int<br /><br />';
    
        echo decode($encoded); // STILL WORKS - HAPPY DAYS!
    ?>