Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 275
kryo Avatar asked Jun 01 '14 17:06

kryo


2 Answers

$x = encode(10000000000000);
var_dump($x); //float(1.1710299640683E-305)
echo decode($x); //10000000000000

$y = (float) "1.1710299640683E-305";
var_dump($y); //float(1.1710299640683E-305)
echo decode($y); //-6629571225977708544 

$z = ($x == $y);
var_dump($z); //false

http://www.php.net/manual/en/language.types.float.php

... never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available. For a "simple" explanation, see the » floating point guide that's also titled "Why don’t my numbers add up?"

like image 159
FuzzyTree Avatar answered Oct 20 '22 02:10

FuzzyTree


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!
?>
like image 44
Jacek Sokolowski Avatar answered Oct 20 '22 04:10

Jacek Sokolowski