且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在PHP(Bitcoin)中读取8字节整数

更新时间:2023-11-14 09:47:58

作为PHP unpack() 的文档功能说明,如果尝试解压缩长度等于PHP本机整数大小(即32或64位,具体取决于PHP的编译方式)的无符号值,则可能会发生奇怪的事情:

请注意,PHP在内部存储带符号的整数值.如果解压缩一个大的无符号长整型,并且其大小与PHP内部存储的值相同,那么即使指定了无符号解压缩,结果也将是负数." >

可以说,这是一个PHP错误:如果我要求一个无符号值,我非常希望得到 get 一个无符号值,即使它必须在内部表示为浮点数也是如此.

>

在任何情况下,解决此怪癖"的一种方法是将二进制字符串解压缩成较小的块,例如每个8或16位,然后手动重新组装它们,如下所示:

 $bin = pack( 'H*', '00743ba40b000000' );

$words = array_reverse( unpack( 'v*', $bin ) );
$n = 0;
foreach ( $words as $word ) {
    $n = (1 << 16) * $n + $word;
}

echo "$n\n";  // prints "50000000000"
 

I am trying to read the value from a Bitcoin transaction - https://en.bitcoin.it/wiki/Transaction

The wiki says it is an 8 byte integer, but nothing I have tried with unpack gives me the right value as a decimal.

I've been able to read everything else from the transaction except the values out the output - given the other fields I have managed to parse all line up with the correct values it appears I am reading the correct 8 bytes.

With:

$bin = hex2bin('00743ba40b000000');
$value = unpack('lvalue', $bin);
// 'value' => int -1539607552

and

$bin = hex2bin('d67e690000000000');
$value = unpack('lvalue', $bin);
// 'value' => int 6913750

That should be 500 and 0.0691375.

As the documentation for PHP's unpack() function notes, strange things may happen if you try to unpack an unsigned value with length equal to PHP's native integer size (i.e. 32 or 64 bits depending on how your PHP was compiled):

"Note that PHP internally stores integral values as signed. If you unpack a large unsigned long and it is of the same size as PHP internally stored values the result will be a negative number even though unsigned unpacking was specified."

Arguably, this is a PHP bug: if I ask for an unsigned value, I damn well expect to get an unsigned value, even if it has to be internally represented as a float.

In any case, one way to work around this "quirk" is to unpack the binary string in smaller chunks of, say, 8 or 16 bits each, and reassemble them manually, like this:

$bin = pack( 'H*', '00743ba40b000000' );

$words = array_reverse( unpack( 'v*', $bin ) );
$n = 0;
foreach ( $words as $word ) {
    $n = (1 << 16) * $n + $word;
}

echo "$n\n";  // prints "50000000000"