Skip to content

Instantly share code, notes, and snippets.

@ngfw
Last active January 9, 2025 23:15
Show Gist options
  • Select an option

  • Save ngfw/4684951 to your computer and use it in GitHub Desktop.

Select an option

Save ngfw/4684951 to your computer and use it in GitHub Desktop.
PHP BaseConverter
<?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)
);
}
}
}
<?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));
}
}
@miladghiravani
Copy link
Copy Markdown

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 than 9223372036854775807

@Stafox
Copy link
Copy Markdown

Stafox commented Apr 4, 2017

Base62::convert(1234567890987654321); //1tckI2JJZGz
Base62::convert('1tckI2JJZGz', 62, 10); // 1234567890987654507 != 1234567890987654321

@Gussoh
Copy link
Copy Markdown

Gussoh commented Nov 21, 2024

Here is another comment pointing out that this conversion is not working correctly.

@ngfw
Copy link
Copy Markdown
Author

ngfw commented Jan 9, 2025

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