64-bit Float Literals PHP
Asked Answered
M

3

10

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

Matte answered 1/6, 2014 at 17:33 Comment(6)
Might I ask what you need this for?Eastbound
I wanted to make use of all 64 bits for my redis zset scores. It appears predis intentionally loses precision. github.com/nicolasff/phpredis/issues/61Matte
So the problem is that that value isn't the same as the encode(...) one, even though it appears that way. echo encode(10000000000000) - 1.1710299640683E-305; // 2.3525429792377E-319 So the question is, do you want the string to be 100% accurate?Talbot
Yes. The idea is that you can convert back and forth.Matte
Would you be interested in an ugly solution? Basically if you take the result of your encode function and add the difference to the end (for example 1.171029964068323525429792377E-305) it is "equal". However changing the rightmost digits also results in "equality".Talbot
I've had some success with printf and php.ini's precision=... However, I've decided to stay away from type conversions while using php from now on.Matte
A
2

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!
?>
Ahumada answered 3/6, 2014 at 19:58 Comment(3)
How can I get a string representation of encode(10000000000000) without losing precision? This must be possible in PHPMatte
Setting precision = 200 in php.ini does not seem to help.Matte
You can try to use printf('%.200f', $encoded); but php will truncate it to 53 anyway. You should even get the notification then: "Requested precision of 200 digits was truncated to PHP maximum of 53 digits in(..)". So you will not be able to do it using standard php solutions (there can be some custom libraries though!).Ahumada
I
4
$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?"

Isochronal answered 1/6, 2014 at 18:16 Comment(3)
Your answer does not solve my problem. Note 1.1710299640683E-305 == 1.1710299640683E-305 returns trueMatte
You're comparing 2 directly defined floats, which will of course be equal. But if you compare a float returned from your decode method to a directly defined float, then these will not be equal for the reasons explained in the link.Isochronal
If two floats have the same binary representation, they should be equal, regardless of whether or not they were defined in the same way. All I want to do is convert a long to a double without changing the bits...Matte
A
2

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!
?>
Ahumada answered 3/6, 2014 at 19:58 Comment(3)
How can I get a string representation of encode(10000000000000) without losing precision? This must be possible in PHPMatte
Setting precision = 200 in php.ini does not seem to help.Matte
You can try to use printf('%.200f', $encoded); but php will truncate it to 53 anyway. You should even get the notification then: "Requested precision of 200 digits was truncated to PHP maximum of 53 digits in(..)". So you will not be able to do it using standard php solutions (there can be some custom libraries though!).Ahumada
M
0

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

Matte answered 6/6, 2014 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.