64位浮点数字面量PHP

10

我正在尝试在php中将一个64位浮点数转换为64位整数(反之亦然)。我需要保留字节,因此我使用pack和unpack函数。我需要的功能基本上类似于Java的Double.doubleToLongBits()方法。http://docs.oracle.com/javase/7/docs/api/java/lang/Double.html#doubleToLongBits(double)

通过查阅php 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];
}

而且这在大多数情况下都运作良好……

echo decode(encode(10000000000000));

一亿

echo encode(10000000000000);

1.1710299640683E-305

但是这里就变得棘手了......

echo decode(1.1710299640683E-305);

-6629571225977708544

我不知道这里出了什么问题。请自行尝试:http://pastebin.com/zWKC97Z7

您需要在Linux上使用64位PHP。该网站似乎模拟了这种设置:http://www.compileonline.com/execute_php_online.php


我可以问一下你需要这个做什么吗? - Daniel W.
我想要利用Redis zset分数的全部64位。但是看起来Predis故意丢失精度。https://github.com/nicolasff/phpredis/issues/61 - kryo
所以问题在于,即使看起来是一样的,但那个值与 encode(...) 的值不同。echo encode(10000000000000) - 1.1710299640683E-305; // 2.3525429792377E-319那么问题是,你是否希望字符串是100%准确的? - Andrew
是的。这个想法是你可以来回转换。 - kryo
你对一个丑陋的解决方案感兴趣吗?基本上,如果你取得你的编码函数的结果并将差异添加到末尾(例如1.171029964068323525429792377E-305),它就是“相等”的。然而,改变最右边的数字也会导致“相等性”。 - Andrew
我在使用 printf 和 php.ini 的 precision= 时取得了一些成功。然而,我决定从现在开始在使用 PHP 时远离类型转换。 - kryo
3个回答

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

不要将浮点数结果信任到最后一位数字,并且不要直接比较浮点数以获得相等。如果需要更高的精度,可以使用任意精度数学函数和gmp函数。有关“简单”解释,请参阅标题为“Why don't my numbers add up?”的»浮点指南。详情请访问:http://www.php.net/manual/en/language.types.float.php

你的回答并没有解决我的问题。注意 1.1710299640683E-305 == 1.1710299640683E-305 返回 true。 - kryo
你正在比较两个直接定义的浮点数,它们当然是相等的。但如果你比较从解码方法返回的浮点数和直接定义的浮点数,则由于链接中所述的原因,它们将不相等。 - FuzzyTree
如果两个浮点数具有相同的二进制表示,那么它们应该是相等的,无论它们是否以相同的方式定义。我想做的就是将一个 long 类型转换为 double 类型,而不改变比特位... - kryo

2
它正常工作,这种情况下唯一的问题在于逻辑:
echo decode(1.1710299640683E-305);

您不能使用“echo”函数的“rounded”和“human readable”输出来解码原始值(因为您会失去这个双精度的精度)。

如果您将encode(10000000000000)的返回保存到变量中,然后尝试再次decode它,它将正常工作(您可以在10000000000000上使用echo而不会失去精度)。

请参见下面的示例,您也可以在PHP编译器上执行:

<?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!
?>

如何在不失精度的情况下获取 encode(10000000000000) 的字符串表示?这在 PHP 中是可行的。 - kryo
php.ini 中设置 precision = 200 似乎没有帮助。 - kryo
你可以尝试使用 printf('%.200f', $encoded);,但是 PHP 无论如何都会将其截断为53位。你甚至会收到这样的通知:"在(...)中,请求精度为200位的数字被截断为PHP最大的53位"。因此,你将无法使用标准的 PHP 解决方案来完成它(虽然可能有一些自定义库!)。 - Jacek Sokolowski

0
如果您有一个可靠的固定小数点,比如我的情况和货币的情况,您可以将浮点数乘以某个10的幂次方(例如,美元的100)。
function encode($float) {
     return (int) $float * pow(10, 2);
}
function decode($str) {
    return bcdiv($str, pow(10, 2), 2);
}

然而,这对于大数不起作用,并且并没有正式解决问题。 在php 5.4中似乎不可能将整数转换为浮点字符串,然后再转回原始整数值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接