[Uid] improve base convertion logic

This commit is contained in:
Nicolas Grekas 2020-03-14 11:48:14 +01:00
parent 42c76d7683
commit 0e05c6de80
4 changed files with 101 additions and 89 deletions

View File

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Uid;
/**
* @internal
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class BinaryUtil
{
public const BASE10 = [
'' => '0123456789',
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
];
public static function toBase(string $bytes, array $map): string
{
$base = \strlen($alphabet = $map['']);
$bytes = array_values(unpack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', $bytes));
$digits = '';
while ($count = \count($bytes)) {
$quotient = [];
$remainder = 0;
for ($i = 0; $i !== $count; ++$i) {
$carry = $bytes[$i] + ($remainder << (\PHP_INT_SIZE >= 8 ? 16 : 8));
$digit = intdiv($carry, $base);
$remainder = $carry % $base;
if ($digit || $quotient) {
$quotient[] = $digit;
}
}
$digits = $alphabet[$remainder].$digits;
$bytes = $quotient;
}
return $digits;
}
public static function fromBase(string $digits, array $map): string
{
$base = \strlen($map['']);
$count = \strlen($digits);
$bytes = [];
while ($count) {
$quotient = [];
$remainder = 0;
for ($i = 0; $i !== $count; ++$i) {
$carry = ($bytes ? $digits[$i] : $map[$digits[$i]]) + $remainder * $base;
if (\PHP_INT_SIZE >= 8) {
$digit = $carry >> 16;
$remainder = $carry & 0xFFFF;
} else {
$digit = $carry >> 8;
$remainder = $carry & 0xFF;
}
if ($digit || $quotient) {
$quotient[] = $digit;
}
}
$bytes[] = $remainder;
$count = \count($digits = $quotient);
}
return pack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', ...array_reverse($bytes));
}
public static function add(string $a, string $b): string
{
$carry = 0;
for ($i = 7; 0 <= $i; --$i) {
$carry += \ord($a[$i]) + \ord($b[$i]);
$a[$i] = \chr($carry & 0xFF);
$carry >>= 8;
}
return $a;
}
}

View File

@ -1,85 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Uid;
/**
* @internal
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InternalUtil
{
public static function toBinary(string $digits): string
{
$bytes = '';
$len = \strlen($digits);
while ($len > $i = strspn($digits, '0')) {
for ($j = 2, $r = 0; $i < $len; $i += $j, $j = 0) {
do {
$r *= 10;
$d = (int) substr($digits, $i, ++$j);
} while ($i + $j < $len && $r + $d < 256);
$j = \strlen((string) $d);
$q = str_pad(($d += $r) >> 8, $j, '0', STR_PAD_LEFT);
$digits = substr_replace($digits, $q, $i, $j);
$r = $d % 256;
}
$bytes .= \chr($r);
}
return strrev($bytes);
}
public static function toDecimal(string $bytes): string
{
$digits = '';
$len = \strlen($bytes);
while ($len > $i = strspn($bytes, "\0")) {
for ($r = 0; $i < $len; $i += $j) {
$j = $d = 0;
do {
$r <<= 8;
$d = ($d << 8) + \ord($bytes[$i + $j]);
} while ($i + ++$j < $len && $r + $d < 10);
if (256 < $d) {
$q = intdiv($d += $r, 10);
$bytes[$i] = \chr($q >> 8);
$bytes[1 + $i] = \chr($q & 0xFF);
} else {
$bytes[$i] = \chr(intdiv($d += $r, 10));
}
$r = $d % 10;
}
$digits .= (string) $r;
}
return strrev($digits);
}
public static function binaryAdd(string $a, string $b): string
{
$sum = 0;
for ($i = 7; 0 <= $i; --$i) {
$sum += \ord($a[$i]) + \ord($b[$i]);
$a[$i] = \chr($sum & 0xFF);
$sum >>= 8;
}
return $a;
}
}

View File

@ -121,7 +121,7 @@ class Ulid implements \JsonSerializable
base_convert(substr($time, 6, 4), 32, 16)
);
return InternalUtil::toDecimal(hex2bin($time)) / 1000;
return BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10) / 1000;
}
public function __toString(): string
@ -163,7 +163,7 @@ class Ulid implements \JsonSerializable
if (\PHP_INT_SIZE >= 8) {
$time = base_convert($time, 10, 32);
} else {
$time = bin2hex(InternalUtil::toBinary($time));
$time = bin2hex(BinaryUtil::fromBase($time, BinaryUtil::BASE10));
$time = sprintf('%s%04s%04s',
base_convert(substr($time, 0, 2), 16, 32),
base_convert(substr($time, 2, 5), 16, 32),

View File

@ -121,10 +121,10 @@ class Uuid implements \JsonSerializable
}
$time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT);
$time = InternalUtil::binaryAdd($time, self::TIME_OFFSET_COM);
$time = BinaryUtil::add($time, self::TIME_OFFSET_COM);
$time[0] = $time[0] & "\x7F";
return InternalUtil::toDecimal($time) / 10000000;
return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000;
}
public function getMac(): string