Last active
January 9, 2025 23:15
-
-
Save ngfw/4684951 to your computer and use it in GitHub Desktop.
PHP BaseConverter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
class BaseConverter | |
{ | |
/** | |
* The character set used for base conversion. | |
* Includes digits 0-9 and lowercase and uppercase letters A-Z. | |
* The minimum base is 2 and the maximum base is 62. | |
*/ | |
private const CHARSET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
/** | |
* The minimum base allowed for base conversion operations. | |
*/ | |
private const MIN_BASE = 2; | |
/** | |
* The maximum base allowed for base conversion operations. | |
*/ | |
private const MAX_BASE = 62; | |
/** | |
* Converts a number from decimal to a specified base. | |
* | |
* This method converts a given number (either as a string or integer) into | |
* a string representation of the number in the target base, ranging from | |
* base 2 to base 62. | |
* | |
* @param string|int $number The number to convert, can be a string or integer. | |
* @param int $base The base to convert the number to (between 2 and 62). | |
* @return string The number represented in the specified base. | |
* @throws InvalidArgumentException If the base is outside the allowed range (2-62) or if the number is negative. | |
*/ | |
public static function toBase(string|int $number, int $base): string | |
{ | |
self::validateBase($base); | |
if (!is_numeric($number) || (int)$number < 0) { | |
throw new InvalidArgumentException('Number must be a non-negative integer.'); | |
} | |
$number = (string)$number; | |
if ($number === '0') { | |
return '0'; | |
} | |
$result = ''; | |
do { | |
$remainder = bcmod($number, (string)$base); | |
$result = self::CHARSET[(int)$remainder] . $result; | |
$number = bcdiv($number, (string)$base, 0); | |
} while (bccomp($number, '0') > 0); | |
return $result; | |
} | |
/** | |
* Converts a number from a specified base to decimal (base 10). | |
* | |
* This method converts a string representation of a number in a specified | |
* base (between 2 and 62) to its decimal (base 10) equivalent. | |
* | |
* @param string $encodedValue The number to convert in the specified base. | |
* @param int $base The base of the number to convert from (between 2 and 62). | |
* @return string The decimal representation of the number as a string. | |
* @throws InvalidArgumentException If the base is outside the allowed range (2-62) or if invalid characters are used. | |
*/ | |
public static function fromBase(string $encodedValue, int $base): string | |
{ | |
self::validateBase($base); | |
$encodedValue = trim($encodedValue); | |
if ($encodedValue === '0') { | |
return '0'; | |
} | |
$decimal = '0'; | |
foreach (str_split($encodedValue) as $char) { | |
$value = strpos(self::CHARSET, $char); | |
if ($value === false || $value >= $base) { | |
throw new InvalidArgumentException( | |
sprintf("Invalid character '%s' for base %d. Allowed characters: %s", | |
$char, | |
$base, | |
substr(self::CHARSET, 0, $base) | |
) | |
); | |
} | |
$decimal = bcadd(bcmul($decimal, (string)$base), (string)$value); | |
} | |
return $decimal; | |
} | |
/** | |
* Converts a number between different bases. | |
* | |
* This method first converts the number from its original base (fromBase) | |
* to decimal, and then converts it to the target base (toBase). | |
* | |
* @param string|int $number The number to convert, can be a string or integer. | |
* @param int $fromBase The base of the original number (default is 10). | |
* @param int $toBase The base to convert the number to (default is 62). | |
* @return string The number represented in the target base. | |
*/ | |
public static function convert(string|int $number, int $fromBase = 10, int $toBase = 62): string | |
{ | |
return self::toBase(self::fromBase((string)$number, $fromBase), $toBase); | |
} | |
/** | |
* Validates the base for a conversion operation. | |
* | |
* This method ensures the base is within the allowed range (between 2 and 62). | |
* | |
* @param int $base The base to validate. | |
* @throws InvalidArgumentException If the base is outside the allowed range (2-62). | |
*/ | |
private static function validateBase(int $base): void | |
{ | |
if ($base < self::MIN_BASE || $base > self::MAX_BASE) { | |
throw new InvalidArgumentException( | |
sprintf('Base must be between %d and %d.', self::MIN_BASE, self::MAX_BASE) | |
); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# /tests/BaseConverterTest.php | |
use PHPUnit\Framework\TestCase; | |
class BaseConverterTest extends TestCase | |
{ | |
public function testToBaseWithValidInput() | |
{ | |
$this->assertEquals('1010', BaseConverter::toBase(10, 2)); | |
$this->assertEquals('a', BaseConverter::toBase(10, 62)); | |
$this->assertEquals('z', BaseConverter::toBase(35, 36)); | |
} | |
public function testToBaseWithZero() | |
{ | |
$this->assertEquals('0', BaseConverter::toBase(0, 2)); | |
$this->assertEquals('0', BaseConverter::toBase(0, 16)); | |
$this->assertEquals('0', BaseConverter::toBase(0, 62)); | |
} | |
public function testToBaseWithInvalidBase() | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
$this->expectExceptionMessage('Base must be between 2 and 62.'); | |
BaseConverter::toBase(10, 1); | |
} | |
public function testToBaseWithNegativeNumber() | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
$this->expectExceptionMessage('Number must be a non-negative integer.'); | |
BaseConverter::toBase(-10, 16); | |
} | |
public function testFromBaseWithValidInput() | |
{ | |
$this->assertEquals(10, BaseConverter::fromBase('1010', 2)); | |
$this->assertEquals(10, BaseConverter::fromBase('a', 16)); | |
$this->assertEquals(35, BaseConverter::fromBase('z', 36)); | |
$this->assertEquals(20, BaseConverter::fromBase('k', 62)); | |
} | |
public function testFromBaseWithZero() | |
{ | |
$this->assertEquals(0, BaseConverter::fromBase('0', 2)); | |
$this->assertEquals(0, BaseConverter::fromBase('0', 16)); | |
$this->assertEquals(0, BaseConverter::fromBase('0', 62)); | |
} | |
public function testFromBaseWithInvalidBase() | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
$this->expectExceptionMessage('Base must be between 2 and 62.'); | |
BaseConverter::fromBase('10', 63); | |
} | |
public function testFromBaseWithInvalidCharacter() | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
$this->expectExceptionMessage("Invalid character 'Z' for base 16."); | |
BaseConverter::fromBase('1Z', 16); | |
} | |
public function testConvertBetweenBases() | |
{ | |
$this->assertEquals('1010', BaseConverter::convert('a', 16, 2)); | |
$this->assertEquals('a', BaseConverter::convert('1010', 2, 16)); | |
$this->assertEquals('a', BaseConverter::convert('1010', 2, 62)); | |
$this->assertEquals('20', BaseConverter::convert('k', 62, 10)); | |
} | |
public function testConvertWithDefaultBases() | |
{ | |
$this->assertEquals('a', BaseConverter::convert(10)); | |
$this->assertEquals('a', BaseConverter::convert(10, 10, 16)); | |
} | |
public function testConvertWithInvalidInput() | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
BaseConverter::convert('G', 16, 2); | |
} | |
public function testBasic2() | |
{ | |
$this->assertEquals('1tckI2JJZDz', BaseConverter::convert(1234567890987654321)); | |
$this->assertEquals(1234567890987654321, BaseConverter::convert('1tckI2JJZDz', 62, 10)); | |
} | |
public function testSTR() | |
{ | |
$this->assertEquals('Nick', BaseConverter::convert(11748028)); | |
$this->assertEquals(11748028, BaseConverter::convert('Nick', 62, 10)); | |
} | |
} |
Base62::convert(1234567890987654321); //1tckI2JJZGz
Base62::convert('1tckI2JJZGz', 62, 10); // 1234567890987654507 != 1234567890987654321
Here is another comment pointing out that this conversion is not working correctly.
Thank you for reporting the issue, I hope this helps improve the conversion accuracy and handle larger numbers.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If your OS is 32 bit, this class support lower than integer
214748364
and if your OS is 64 bit, works well for integer lower than9223372036854775807