64位浮点文字PHP

64位浮点文字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

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 http://www.compileonline.com/execute_php_online.php 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!
?>

$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?"

If you have a reliable fixed decimal point, like in my case and the case of currency, you can multiply your float by some power of 10 (ex. 100 for dollars).

function encode($float) {
     return (int) $float * pow(10, 2);
}
function decode($str) {
    return bcdiv($str, pow(10, 2), 2);
}

However, this doesn't work for huge numbers and doesn't officially solve the problem. Seems like it's impossible to convert from an integer to a float string and back without losing the original integer value in php 5.4