PHP 5.3.6 32-битный (переход на 64-битный невозможен).
Нужно сравнить 2 значения uint64
(8-байтовые целые числа без знака). Один из них я получаю как строка а другой как двоичная строка.
Можно ли конвертировать строка представление uint64
в массив из 8 байтов, или преобразовать массив 8 байтов в строка с uint64
на PHP 32bit?
Я старался base_convert
функция для сравнения строковых представлений base-2 и получения странных результатов. Я знаю, что байтовый массив содержит то же самое uint64
как соответствующая строка. Но я понятия не имею, как обеспечить, чтобы они представляли одинаковое число.
Это тестовый код с некоторыми реальными значениями для иллюстрации проблемы:
function byte_to_base2string($byte)
{
$byte = base_convert($byte, 10, 2);
$byte = str_pad($byte, 8, '0', STR_PAD_LEFT);
return $byte;
}
function print_info($base10_string1, $bin_string2)
{
$bin_string1 = null; // TODO: how to obtain it?
$base2_string1 = base_convert($base10_string1, 10, 2);
$base2_string1 = str_pad($base2_string1, 64, '0', STR_PAD_LEFT);
$base2_string2 = array_map('byte_to_base2string', $bin_string2);
$base2_string2 = implode('', $base2_string2);
$base10_string2 = base_convert($base2_string2, 2, 10);
echo sprintf("Wrong base-2 string:\n%s\t%s\n", $base10_string1, $base2_string1);
echo sprintf("base-2 string matches $base10_string1, but base-10 string does not\n%s\t%s\n", $base10_string2, $base2_string2);
echo "\n";
// Can't compare because:
// $base2_string1 != $base2_string2
// $base10_string1 != $base10_string2
// $bin_string1 no idea how to convert
}
$strings = [
'288512493108985552',
'288512958990381002',
'288512564016815754'
];
// obtained via unpack('C*', $binaryStr)
$bytes = [
[4, 1, 0, 149, 121, 5, 254, 208],
[4, 1, 1, 1, 241, 183, 239, 202],
[4, 1, 0, 165, 251, 117, 158, 138]
];
array_map('print_info', $strings, $bytes);
И вывод:
Wrong base-2 string:
288512493108985552 0000010000000001000000001001010101111001000001011111111011000000
base-2 string matches 288512493108985552, but base-10 string does not
288512493108985526 0000010000000001000000001001010101111001000001011111111011010000
Wrong base-2 string:
288512958990381002 0000010000000001000000010000000111110001101101111110111111000000
base-2 string matches 288512958990381002, but base-10 string does not
288512958990381002 0000010000000001000000010000000111110001101101111110111111001010
Wrong base-2 string:
288512564016815754 0000010000000001000000001010010111111011011101011001111010000000
base-2 string matches 288512564016815754, but base-10 string does not
288512564016815764 0000010000000001000000001010010111111011011101011001111010001010
Нашел решение (см. Мой ответ ниже), но не уверен, что это лучший способ. Все еще надеюсь найти что-то более ясное и прямое.
Что ж, спасибо хорошему человеку в комментариях к PHP. Функция в этот комментарий делает задачу Я не мог найти дорогу только потому, что я забыл о BCMath.
Так что на данный момент мой рабочий ответ (используя convBase()
из комментария):
$id1 = "..."; // string representation of uint64 value
$id2 = [...]; // array of bytes
// convert to base-2 string
$id1 = convBase($id1, '0123456789', '01');
// convert each byte to base-2 string (8 chars) and join them
$id2 = implode('', array_map(function($b) {
$b = convBase($b, '0123456789', '01');
$b = str_pad($b, 8, '0', STR_PAD_LEFT);
return $b;
}, $id2));
// pad with leading zeroes to equal length
$len = max(strlen($id1), strlen($id2));
$id1 = str_pad($id1, $len, '0', STR_PAD_LEFT);
$id2 = str_pad($id2, $len, '0', STR_PAD_LEFT);
// Now its OK!
$id1 === $id2;
convBase($id1, '01', '0123456789') === convBase($id2, '01', '0123456789');
Больной место скопируйте-вставьте исходный код функции здесь. Так, на всякий случай 😉
function convBase($numberInput, $fromBaseInput, $toBaseInput)
{
if ($fromBaseInput == $toBaseInput) return $numberInput;
$fromBase = str_split($fromBaseInput, 1);
$toBase = str_split($toBaseInput, 1);
$number = str_split($numberInput, 1);
$fromLen = strlen($fromBaseInput);
$toLen = strlen($toBaseInput);
$numberLen = strlen($numberInput);
$retval = '';
if ($toBaseInput == '0123456789') {
$retval = 0;
for ($i = 1; $i <= $numberLen; $i++)
$retval = bcadd($retval, bcmul(array_search($number[$i - 1], fromBase), bcpow($fromLen, $numberLen - $i)));
return $retval;
}
if ($fromBaseInput != '0123456789')
$base10 = convBase($numberInput, $fromBaseInput, '0123456789');
else
$base10 = $numberInput;
if ($base10 < strlen($toBaseInput))
return $toBase[$base10];
while ($base10 != '0') {
$retval = $toBase[bcmod($base10, $toLen)] . $retval;
$base10 = bcdiv($base10, $toLen, 0);
}
return $retval;
}
PS: Извините за возможные опечатки или ошибки в моем коде, я написал его довольно быстро и не тестировал, просто чтобы проиллюстрировать решение.
Доступна ли библиотека GMP в вашей версии PHP? http://php.net/manual/en/intro.gmp.php
Он может сделать всю работу всего за 3 строки:
$a = gmp_init('892348924892894240808924308925', 10);
$b = gmp_init('111111111010101010101011110101010101001010101010101010101011', 2);
var_dump(gmp_compare($a, $b));