Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP 32bit. How to compare uint64 in string and binary representation?

Tags:

Prerequisites

PHP 5.3.6 32-bit (moving to 64-bit is not possible).

Need to compare 2 values uint64 (8-byte unsigned integers). One of them I get as string and another as binary string.

Question

Is it possible to convert string representation of uint64 to array of 8 bytes, or convert array of 8 bytes into string with uint64 on PHP 32bit?

Illustration

I tried base_convert function to compare base-2 string representations and got weird results. I know that byte array contains the same uint64 as the corresponding string. But I have no idea on how to ensure that they do represent the same number.

This is test code with some real values to illustrate the problem:

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);

And the output is:

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

Updated

Found a solution (see my answer below), but not sure if it is the best way. Still hope to find something more clear and straight.

like image 736
Евгений Савичев Avatar asked Aug 19 '16 12:08

Евгений Савичев


2 Answers

Well, thanks to a good man in PHP manual comments. Function in this comment does the task. I could not find the way just because I forgot about bcmath.

So at the moment my working answer is (using convBase() from the comment):

$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');

I'll place copy-paste function source code here. Just in case ;)

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: Sorry for possible typos or errors in my code, I wrote it quite fast and did not test, just to illustrate the solution.

like image 163
Евгений Савичев Avatar answered Sep 23 '22 16:09

Евгений Савичев


Is GMP library available in your PHP version? http://php.net/manual/en/intro.gmp.php It can do all the job just in 3 strings:

$a = gmp_init('892348924892894240808924308925', 10);
$b = gmp_init('111111111010101010101011110101010101001010101010101010101011', 2);

var_dump(gmp_compare($a, $b));
like image 36
Max Zuber Avatar answered Sep 25 '22 16:09

Max Zuber