Added generic IntegerBaseConverter implementation and merged Base62 class with XNumber.

The Base62 implementation was always a bit misleading because both UriBase64 and Base32 handle any binary data whereas Base62 was only for converting integers.
This shake-up should lessen that confusion.
This commit is contained in:
Pachira 2024-07-31 19:05:41 +00:00
parent 94cd311f8c
commit 4a40868f58
5 changed files with 135 additions and 61 deletions

View file

@ -1 +1 @@
0.2407.311838
0.2407.311904

View file

@ -0,0 +1,100 @@
<?php
// IntegerBaseConverter.php
// Created: 2024-07-31
// Updated: 2024-07-31
namespace Index;
use InvalidArgumentException;
/**
* Provides methods for encoding integers in different bases.
*/
class IntegerBaseConverter {
private int $maxBase;
/**
* @param string $characterSet Specifies the provided character set, must be a string consisting out of at least two unique characters.
* @throws InvalidArgumentException If $characterSet contains less than two characters.
* @throws InvalidArgumentException If $characterSet contains duplicate characters.
*/
public function __construct(
private string $characterSet
) {
$length = strlen($characterSet);
if($length < 2)
throw new InvalidArgumentException('$characterSet must contain at least two characters');
if(XString::countUnique($characterSet) !== $length)
throw new InvalidArgumentException('$characterSet must contain a set of entirely unique characters');
$this->maxBase = $length;
}
/**
* Converts a positive base10 integer to another base.
*
* @param int $integer The integer to encode.
* @param int $toBase Target base, 0 for the base of the provided character set.
* @return string The encoded data, as a string.
*/
public function encode(int $integer, int $toBase = 0): string {
if($integer < 0)
throw new InvalidArgumentException('$integer contains a negative value, which cannot be represented');
if($toBase === 0)
$toBase = $this->maxBase;
else {
if($toBase < 2)
throw new InvalidArgumentException('lowest supported value for $toBase is 2');
if($toBase > $this->maxBase)
throw new InvalidArgumentException('specified $toBase value is greater than the amount of characters in the provided character set');
}
if($integer === 0)
return '';
$output = '';
for($i = floor(log10($integer) / log10($toBase)); $i >= 0; --$i) {
$exp = $toBase ** $i;
$index = (int)floor($integer / $exp);
$output .= $this->characterSet[$index];
$integer -= $index * $exp;
}
return $output;
}
/**
* Converts another base encoded integer to a base10 integer.
*
* @param string $string The encoded integer.
* @param int $fromBase Source base, 0 for the base of the provided character set.
* @return int|false Returns the decoded integer or false on failure.
*/
public function decode(string $string, int $fromBase = 0): int|false {
if($fromBase === 0)
$fromBase = $this->maxBase;
else {
if($fromBase < 2)
throw new InvalidArgumentException('lowest supported value for $fromBase is 2');
if($fromBase > $this->maxBase)
throw new InvalidArgumentException('specified $fromBase value is greater than the amount of characters in the provided character set');
}
if($string === '')
return 0;
$output = 0;
$length = strlen($string) - 1;
for($i = 0; $i <= $length; ++$i) {
$pos = strpos($this->characterSet, $string[$i]);
if($pos === false)
return false;
$output += $pos * ($fromBase ** ($length - $i));
}
return $output;
}
}

View file

@ -1,53 +0,0 @@
<?php
// Base62.php
// Created: 2022-01-28
// Updated: 2024-07-31
namespace Index\Serialisation;
/**
* Provides a Base62 serialiser.
*/
final class Base62 {
private const CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
private const BASE = 62;
/**
* Converts a base10 integer to a base62 integer.
*
* @param int $integer The integer to encode.
* @return string The encoded data, as a string.
*/
public static function encode(int $integer): string {
$output = '';
for($i = floor(log10($integer) / log10(self::BASE)); $i >= 0; --$i) {
$index = (int)floor($integer / (self::BASE ** $i));
$output .= substr(self::CHARS, $index, 1);
$integer -= $index * (self::BASE ** $i);
}
return $output;
}
/**
* Converts a base62 integer to a base10 integer.
*
* @param string $string The encoded integer.
* @return int|false Returns the decoded integer or false on failure.
*/
public static function decode(string $string): int|false {
$output = 0;
$length = strlen($string) - 1;
for($i = 0; $i <= $length; ++$i) {
$pos = strpos(self::CHARS, $string[$i]);
if($pos === false)
return false;
$output += $pos * (self::BASE ** ($length - $i));
}
return $output;
}
}

View file

@ -1,16 +1,43 @@
<?php
// XNumber.php
// Created: 2023-09-15
// Updated: 2023-11-09
// Updated: 2024-07-31
namespace Index;
use InvalidArgumentException;
/**
* Provides various helper methods for numbers.
*/
final class XNumber {
public const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
private static ?IntegerBaseConverter $base62Converter = null;
private static function getBase62Converter(): IntegerBaseConverter {
self::$base62Converter ??= new IntegerBaseConverter(self::BASE62);
return self::$base62Converter;
}
/**
* Converts a base10 integer to a base62 integer.
*
* @param int $integer The integer to encode.
* @return string The encoded data, as a string.
*/
public static function toBase62(int $integer): string {
return self::getBase62Converter()->encode($integer);
}
/**
* Converts a base62 integer to a base10 integer.
*
* @param string $string The encoded integer.
* @return int|false Returns the decoded integer or false on failure.
*/
public static function fromBase62(string $string): int|false {
return self::getBase62Converter()->decode($string);
}
public static function weighted(float $num1, float $num2, float $weight): float {
$weight = min(1, max(0, $weight));
return ($num1 * $weight) + ($num2 * (1 - $weight));

View file

@ -7,9 +7,9 @@ declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Serialisation\Base62;
use Index\XNumber;
#[CoversClass(Base62::class)]
#[CoversClass(XNumber::class)]
final class Base62Test extends TestCase {
public const TESTS = [
['aaaaaa', 9311514030],
@ -25,11 +25,11 @@ final class Base62Test extends TestCase {
public function testDecode(): void {
foreach(self::TESTS as $test)
$this->assertEquals($test[1], Base62::decode($test[0]));
$this->assertEquals($test[1], XNumber::fromBase62($test[0]));
}
public function testEncode(): void {
foreach(self::TESTS as $test)
$this->assertEquals($test[0], Base62::encode($test[1]));
$this->assertEquals($test[0], XNumber::toBase62($test[1]));
}
}