PHP 32 бит. Как сравнить uint64 в строковом и бинарном представлении?

Предпосылки

PHP 5.3.6 32-бит (переход на 64-бит невозможен).

Нужно сравнить 2 значения uint64 (8-байтовые целые числа без знака). Один из них я получаю как строку, а другой как двоичную строку.

Вопрос

Можно ли преобразовать строковое представление uint64 в массив из 8 байт или преобразовать массив из 8 байт в строку с uint64 на 32-битном PHP?

Иллюстрация

Я попробовал функцию 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

Обновлено

Нашел решение (см. мой ответ ниже), но не уверен, что это лучший способ. Все еще надеюсь найти что-то более ясное и прямое.


person Евгений Савичев    schedule 19.08.2016    source источник
comment
попробуйте использовать функцию bindec() для двоичных строк. Что это даст? The function can convert numbers that are too large to fit into the platforms integer type, larger values are returned as float in that case.   -  person ineersa    schedule 19.08.2016
comment
@ineersa, спасибо за предложение, но bindec, согласно документам, преобразует двоичное число в целое число или, если необходимо по причинам размера, с плавающей запятой И, да, это дает что-то вроде 2.8851249310899E+17 вместо 288512493108985552, теряя точность (как и сказано в руководстве).   -  person Евгений Савичев    schedule 19.08.2016


Ответы (2)


Что ж, спасибо хорошему человеку в комментариях по 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: Извините за возможные опечатки или ошибки в моем коде, я написал его довольно быстро и не тестировал, просто чтобы проиллюстрировать решение.

person Евгений Савичев    schedule 19.08.2016

Доступна ли библиотека 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));
person Max Zuber    schedule 19.08.2016
comment
К сожалению нет. Приложение работает в Google Cloud App Engine. Только бкмат. Но спасибо, я должен присмотреться к gmp, это решение намного лучше, чем пользовательский конвертер. - person Евгений Савичев; 19.08.2016