5

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.

  • try to use `bindec()` function on binary strings. What it will give? `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.` – ineersa Aug 19 '16 at 12:46
  • @ineersa, thanks for suggestion, but `bindec`, according to docs _converts a binary number to an **integer** or, if needed for size reasons, **float**_ And, yes, it gives something like `2.8851249310899E+17` instead of `288512493108985552`, loosing precision (just as manual says). – Евгений Савичев Aug 19 '16 at 14:01

2 Answers2

3

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.

1

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));
Max Zuber
  • 1,217
  • 10
  • 16