如何在PHP中获取浮点数的二进制表示?

11

有没有办法在PHP中获取浮点数的二进制表示?类似于Java的Double.doubleToRawLongBits()

给定一个正的浮点数,我想要得到比该数字小的最大可表示浮点数。在Java中,我可以这样做:

double x = Double.longBitsToDouble(Double.doubleToRawLongBits(d) - 1);

但是我在PHP中没有看到类似的东西。


我非常确定你的Java代码有漏洞。当只设置低位时会发生什么?我怀疑忽略指数不起作用...或者当你从指数0到-1时,你会破坏NaN等位吗? - derobert
1
@derobert:如果你能找到我的Java代码无法处理的情况,除了±0、±Inf或NaN,请告诉我(对于负值,它会给出大于给定值的最小值)。正IEEE浮点数在转换为整数时是有序的。(而负值则是反向排序)。指数以偏移量的无符号值存储,而不是二进制补码,因此-1实际上比0小1。如果只设置了低位,则相当于Double.MIN_VALUE,减去1得到0,这是正确的答案。 - Jenni
1
一个非常好的关于此的文章可以在这里找到:http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - Jenni
谢谢你的解释和教育,这听起来实际上是可以工作的。另外,你应该把你的代码放在一个答案中。回答自己的问题没有什么问题。 - derobert
@derobert - 好的,我把我的解决方案移到了答案中。 - Jenni
4个回答

6

这里是我使用Peter Bailey的建议提出的解决方案。它需要PHP的64位版本。我不保证它在任何方面都是生产质量的,但我分享它以便有人想要进一步完善它。(事实上,我在发布问题后最终做了完全不同的事情,但我仍然将其留在这里作为一个思维锻炼。)

// Returns the largest double-precision floating-point number which
// is less than the given value. Only works for positive values.
// Requires integers to be represented internally with 64 bits (on Linux
// this usually means you're on 64-bit OS with PHP built for 64-bit OS).
// Also requires 64-bit double-precision floating point numbers, but I
// think this is the case pretty much everywhere.
// Returns false on error.
function prevDouble($d) {
  $INT32_MASK = 0xffffffff;
  if((0x1deadbeef >> 32) !== 1) {
    echo 'error: only works on 64-bit systems!';
    return false;
  }
  if($d <= 0) {
    return false;
  }
  $beTest = bin2hex(pack('d', 1.0)); //test for big-endian
  if(strlen($beTest) != 16) {
    echo 'error: system does not use 8 bytes for double precision!';
    return false;
  }

  if($beTest == '3ff0000000000000') {
    $isBE = true;
  }
  else if($beTest == '000000000000f03f') {
    $isBE = false;
  }
  else {
    echo 'error: could not determine endian mode!';
    return false;
  }

  $bin = pack('d', $d);

  //convert to 64-bit int
  $int = 0;
  for($i = 0; $i < 8; $i++)
    $int = ($int << 8) | ord($bin[$isBE ? $i : 7 - $i]);

  $int--;
  //convert back to double
  if($isBE)
    $out = unpack('d', pack('N', ($int >> 32) & $INT32_MASK) . pack('N', $int & $INT32_MASK));
  else
    $out = unpack('d', pack('V', $int & $INT32_MASK) . pack('V', ($int >> 32) & $INT32_MASK));

  return $out[1];
}

6
作为对标题的另一个回答,如果您想查看浮点数的二进制表示:
function floatToBinStr($value) {
   $bin = '';
    $packed = pack('d', $value); // use 'f' for 32 bit
    foreach(str_split(strrev($packed)) as $char) {
        $bin .= str_pad(decbin(ord($char)), 8, 0, STR_PAD_LEFT);
    }
    return $bin;
}

echo floatToBinStr(0.0000000000000000000000000000000000025).PHP_EOL;
echo floatToBinStr(0.25).PHP_EOL;
echo floatToBinStr(0.5).PHP_EOL;
echo floatToBinStr(-0.5).PHP_EOL;

输出:

0011100010001010100101011010010110110111111110000111101000001111
0011111111010000000000000000000000000000000000000000000000000000
0011111111100000000000000000000000000000000000000000000000000000
1011111111100000000000000000000000000000000000000000000000000000

抱歉,为什么在拆分之前要执行strrev($packed)操作? - tonix
1
@user3019105,很久以前...我认为字节(而不是位!)的存储方向与通常在示例中显示的相反。例如:http://www.binaryconvert.com/result_float.html?decimal=048046050053 - flori
如果我使用pack("d",0.25)来包装32位浮点数,那么得到的二进制字符串应该是这个:00000000 00000000 00000001 01111100,而不是这个:00111110 10000000 00000000 00000000,对吗?为什么会这样? - tonix
1
或许这就是 PHP 中浮点数的内在表示方式?(也许像 pack() 文档中 "d" 所述,甚至是机器相关的?)要小心,你在示例中将整行位镜像,但只有块/字节被镜像,块/字节中的位不会。 - flori
好的,我明白了,就像当我使用strrev('abc')== 'cba'时,只有块被镜像(每个字符是1个字节)。 - tonix
显示剩余2条评论

2

这并不是一个完整的答案,但我所知道的将浮点数转换为二进制的唯一方法是使用pack()函数。


看起来这个可能可行,但我需要试一下……如果我成功了,我会发布更新的。 - Jenni
根据您的建议,我找到了一个解决方案。虽然它不太便携,但我最终采取了完全不同的方法,但至少我发布了我得到的工作成果,以防对其他人有所裨益。 - Jenni

0

Java:

long lnBits = Double.doubleToLongBits(longValue);
Byte[] bits = new byte [] {
   (byte) ((value << 56) >>> 56),
   (byte) ((value << 48) >>> 56),
   (byte) ((value << 40) >>> 56),
   (byte) ((value << 32) >>> 56),
   (byte) ((value << 24) >>> 56),
   (byte) ((value << 16) >>> 56),
   (byte) ((value << 8) >>> 56),
   (byte) (value >>> 56)}

PHP:

$bits = $bitsFromJava;
$str="";
for($i=0;$i<8;i++){
    $str.=chr($bits[$i]);
}
$longValue=unpack('d',$str);

$bitsToJava=array();
for(str_split(pack($longValue)) as $chr){
    $bitsToJava[]=ord($chr);
}

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