34

I have a number in base 10. Is there anyway to translate it to a base 62?

Example:

echo convert(12324324);
// returns Yg3 (fantasy example here)

PHP's base_convert() can convert up to base 36.

hakre
  • 193,403
  • 52
  • 435
  • 836
dynamic
  • 46,985
  • 55
  • 154
  • 231

10 Answers10

47

OLD: A quick and dirty solution can be to use a function like this:

function toChars($number) {
   $res = base_convert($number, 10,26);
   $res = strtr($res,'0123456789','qrstuvxwyz');
   return $res;
}

The base convert translate your number to a base where the digits are 0-9a-p then you get rid of the remaining digits with a quick char substitution.

As you may observe, the function is easily reversible.

function toNum($number) {
   $res = strtr($number,'qrstuvxwyz','0123456789');
   $res = base_convert($number, 26,10);
   return $res;
}

By the way, what would you use this function for?


Edit:

Based on the question change and on the @jnpcl answer, here is a set of functions that performs the base conversion without using pow and log (they take half the time to complete the tests).

The functions work for integer values only.

function toBase($num, $b=62) {
  $base='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $r = $num  % $b ;
  $res = $base[$r];
  $q = floor($num/$b);
  while ($q) {
    $r = $q % $b;
    $q =floor($q/$b);
    $res = $base[$r].$res;
  }
  return $res;
}

function to10( $num, $b=62) {
  $base='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $limit = strlen($num);
  $res=strpos($base,$num[0]);
  for($i=1;$i<$limit;$i++) {
    $res = $b * $res + strpos($base,$num[$i]);
  }
  return $res;
}

The test:

for ($i = 0; $i<1000000; $i++) {
  $x =  toBase($i);
  $y =  to10($x);
  if ($i-$y)
    echo "\n$i -> $x -> $y";
}
Eineki
  • 14,773
  • 6
  • 50
  • 59
  • Its explained in the comments of the question. – KingCrunch Feb 11 '11 at 00:32
  • Just seen the modified question. Thanks for the advice – Eineki Feb 11 '11 at 00:42
  • 1
    Your toBase function appears to return a slightly different result than the one built into php for bases below 36. Why is that? – Mike Mar 23 '11 at 14:59
  • @Mike Have you some input samples to let me test the behaviour? – Eineki Mar 23 '11 at 15:42
  • 1
    I just figured out my problem. PHP's mod function apparently has issues with numbers over 2^31. Using the bcmod function fixed the problem. – Mike Mar 23 '11 at 16:11
  • Isn't there a '(' missing on this line: $base='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); – ToddB Feb 08 '13 at 20:34
  • 1
    @ToddB: you are correcyt. Fixed – Eineki Feb 12 '13 at 16:30
  • 2
    Yeah, well working: http://codepad.org/Lj9qRd2n (I removed the `if($i-$y)` check) – Gottlieb Notschnabel Dec 17 '13 at 12:03
  • 4
    This "quick and dirty" solution doesn't do what's asked and isn't reversible. The question was to code in Base 62. – Dan Horvat Jun 30 '15 at 09:50
  • @DanHorvat The quick and dirty solution was aimed to answer the original question. Why do you think it is not reversible? Have you any number (>2^31) that fails the test of going through toChars and toNum functions without remain unchanged? – Eineki Jul 04 '15 at 19:33
  • @Eineki Thanks, works great using this logic in C# https://gist.github.com/herman1vdb/8c0677120f67e5f4a8a6 , (only need it for positive integers... :D) – Aldracor Mar 14 '16 at 10:58
  • 1
    Perhaps http://www.lalit.org/lab/base62-php-convert-number-to-base-62-for-short-urls/ could provide a better solution. – Fandi Susanto May 11 '16 at 16:39
  • @KenyakornKetsombut I have problem for number higher than `2147483648`. how to use this function for higher than this? I try http://www.lalit.org/lab/base62-php-convert-number-to-base-62-for-short-urls/ but this class have this problem. – Milad Ghiravani Oct 24 '16 at 04:53
  • 1
    @mghhgm I need more details to be of any help. I can just guess it is a type casting issue: 2147483648 is a peculiar number. Have you seen php.net/manual/en/language.types.integer.php ? Are you using a 32bit system? How it misbehave? Try to change the line occurence of `$num % $b` with `fmod($num, $b)` and `$num/$b` (and similar ones) with `intdiv($nub, $b)`. They are slower but, on 32bit system should solve the casting type problem I hope you are facing. – Eineki Oct 24 '16 at 17:06
  • @KenyakornKetsombut That's right. I forgot max int support on 32bit is `2147483647`. I test this code on linux server and check this for big number like `123456789876543210` and work correctly! Thank you very much ❤️❤️ – Milad Ghiravani Oct 24 '16 at 18:18
  • @Eineki this will miss the first character. FOr ex at 62 the result will be encoding will be 10 so entire 0 series 0, 0x, 0xx, 0xxx... is missed by this logic. Any fix for this? – Amit Tripathi Nov 25 '17 at 15:11
  • @AmitTripathi I do not understand your question. Have you a number that misbehave? You should post the base 10 representation of this number, the expected value and the wrong result. Can you post one (or more)? – Eineki Nov 26 '17 at 19:32
11

A simpler (and possibly faster) implementation that does not use pow nor log:

function base62($num) {
  $index = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $res = '';
  do {
    $res = $index[$num % 62] . $res;
    $num = intval($num / 62);
  } while ($num);
  return $res;
}
instanceof me
  • 38,520
  • 3
  • 31
  • 40
  • It is similar indeed (just replace `62` with `$b`), I read the answers too fast, just a little less verbose. My comparison was with methods that use `pow` and `log`, which are known to be slow, and that comparison has been done in Eineki’s answer. You can delete this one, since it does not provide substantial new information. – instanceof me Oct 06 '14 at 13:48
  • For big numbers using bcmath: `bcscale(0); do { $res = $index[bcmod($q,62)].$res; $q = bcdiv($q,62); } while ($q); bcscale(3);` – Marcos Fernandez Ramos Apr 15 '20 at 00:28
8

http://us3.php.net/manual/en/function.base-convert.php#52450

<?php
// Decimal > Custom
function dec2any( $num, $base=62, $index=false ) {
    if (! $base ) {
        $base = strlen( $index );
    } else if (! $index ) {
        $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ,0 ,$base );
    }
    $out = "";


    // this fix partially breaks when $num=0, but fixes the $num=238328 bug
    // also seems to break (adds a leading zero) at $num=226981 through $num=238327 *shrug*
    // for ( $t = floor( log10( $num ) / log10( $base - 1 ) ); $t >= 0; $t-- ) {

    // original code:
    for ( $t = floor( log10( $num ) / log10( $base ) ); $t >= 0; $t-- ) {
        $a = floor( $num / pow( $base, $t ) );
        $out = $out . substr( $index, $a, 1 );
        $num = $num - ( $a * pow( $base, $t ) );
    }
    return $out;
}
?>

Parameters:

$num - your decimal integer

$base - base to which you wish to convert $num (leave it 0 if you are providing $index or omit if you're using the default (62))

$index - if you wish to use the default list of digits (0-1a-zA-Z), omit this option, otherwise provide a string (ex.: "zyxwvu")

<?php
// Custom > Decimal
function any2dec( $num, $base=62, $index=false ) {
    if (! $base ) {
        $base = strlen( $index );
    } else if (! $index ) {
        $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, $base );
    }
    $out = 0;
    $len = strlen( $num ) - 1;
    for ( $t = 0; $t <= $len; $t++ ) {
        $out = $out + strpos( $index, substr( $num, $t, 1 ) ) * pow( $base, $len - $t );
    }
    return $out;
}
?>

Parameters:

$num - your custom-based number (string) (ex.: "11011101")

$base - base with which $num was encoded (leave it 0 if you are providing $index or omit if you're using default (62))

$index - if you wish to use the default list of digits (0-1a-zA-Z), omit this option, otherwise provide a string (ex.: "abcdef")

drudge
  • 35,471
  • 7
  • 34
  • 45
  • 1
    it doesnt' work for only 1 number: it's 238328. dec2any(238328) returns "00" and any2dec("00") returns 0. DAMN – dynamic Feb 11 '11 at 00:40
  • because of "pow( $base, $len - $t );" in the any2dec – dynamic Feb 11 '11 at 00:55
  • yes, we could add an if in the any2dec to fix this bug, but it's not elegant, anyway i think i have found another function that works 100% but it seems to be slower. if you fix it in an elegant way write here :) – dynamic Feb 11 '11 at 01:08
  • try to use the base10 conversion without using pow in my answer. I'm not sure my dec2any version is quicker than jnpci one – Eineki Feb 11 '11 at 01:49
  • I found a "fix" for the `238328` case, but it seems to break in other places. I don't understand the math behind the algorithm, so I have no idea why it works. Edited my code above. – drudge Feb 11 '11 at 01:51
3

For big numbers, you might want to use the PHP BC library

function intToAny( $num, $base = null, $index = null ) {
    if ( $num <= 0 ) return '0';
    if ( ! $index )
        $index = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    if ( ! $base )
        $base = strlen( $index );
    else
        $index = substr( $index, 0, $base );
    $res = '';
    while( $num > 0 ) {
        $char = bcmod( $num, $base );
        $res .= substr( $index, $char, 1 );
        $num = bcsub( $num, $char );
        $num = bcdiv( $num, $base );
    }
    return $res;
}
ukautz
  • 2,193
  • 1
  • 13
  • 7
  • 1
    $res .= substr( $index, $char, 1 ); should be $res = substr( $index, $char, 1 ) . $res; – Lance Rushing Mar 27 '15 at 03:57
  • I tried to convert something to base62, and then convert it back to base10. It returned a 0. So, this is a one-way thing, evidently. Also, it would be nice to use this for providing shortcodes in a website, where I could scramble the $index. However, without reversability, it makes it kind of tough to find an ID in a table unless I use the shortcode as the ID. – Volomike Apr 11 '16 at 19:00
  • as @Lance Rushing pointed your return is inverted, you can apply his fix or strrev in the final return – Marcos Fernandez Ramos Apr 15 '20 at 00:25
3

This function output the same than GNU Multiple Precision if possible…

<?php

function base_convert_alt($val,$from_base,$to_base){
static $gmp;
static $bc;
static $gmp62;
if ($from_base<37) $val=strtoupper($val);
if ($gmp===null) $gmp=function_exists('gmp_init');
if ($gmp62===null) $gmp62=version_compare(PHP_VERSION,'5.3.2')>=0;
if ($gmp && ($gmp62 or ($from_base<37 && $to_base<37)))
return gmp_strval(gmp_init($val,$from_base),$to_base);
if ($bc===null) $bc=function_exists('bcscale');
$range='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($from_base==10)
$base_10=$val;
else
{
$n=strlen(($val="$val"))-++$ratio;
if ($bc) for($i=$n;$i>-1;($ratio=bcmul($ratio,$from_base)) && $i--)
$base_10=bcadd($base_10,bcmul(strpos($range,$val[$i]),$ratio));
else for($i=$n;$i>-1;($ratio*=$from_base) && $i--)
$base_10+=strpos($range,$val[$i])*$ratio;
}
if ($bc)
do $result.=$range[bcmod($base_10,$to_base)];
while(($base_10=bcdiv($base_10,$to_base))>=1);
else
do $result.=$range[$base_10%$to_base];
while(($base_10/=$to_base)>=1);
return strrev($to_base<37?strtolower($result):$result);
}


echo base_convert_alt('2661500360',7,51);

// Output Hello
Php'Regex
  • 213
  • 3
  • 4
2
function convertBase10ToBase62($num){
    $charset="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $endChar=$charset[$num%62];
    $rtn="";

    if ( $num == "62" ) { 
        $rtn=$rtn.$charset[1]; 
    } else if ( $num >= 62 ) { 
        $rtn=$rtn.$charset[intval($num/62)%62+1]; 
    }

    $num=intval($num/62);

    while ($num > 61) {
        if ( is_int($num/62) == true ) { 
            $rtn=$rtn.$charset[0]; 
        } else { 
            $rtn=$rtn.$charset[$num%62]; 
        }

        $num=intval($num/62);
    }
    $rtn=$rtn.$endChar;
    echo "\n";
    echo $rtn;

    return $rtn;
}
Jomoos
  • 12,823
  • 10
  • 55
  • 92
Doug
  • 21
  • 1
1

It was hardly tested and works on real big product. Just copy this functions and use. If needed, you can arrange $baseChars sequentially, I need it for blended.

    /**
     * decToAny converter
     * 
     * @param integer $num
     * @param string $baseChars
     * @param integer $base
     * @return string
     */
    function decToAny($num, $baseChars = '', $base = 62, $index = false) {

        $baseChars = empty($baseChars) ? 'HbUlYmGoAd0ScKq6Er5PuZp3OsQCh4RfNMtV8kJiLv9yXeI1aWgFj2zTx7DnBw' : $baseChars;
        if (!$base) {
            $base = strlen($index);
        } else if (!$index) {
            $index = substr($baseChars, 0, $base);
        }
        $out = "";

        for ($t = floor(log10($num) / log10($base)); $t >= 0; $t--) {
            $a = floor($num / pow($base, $t));
            $out = $out . substr($index, $a, 1);
            $num = $num - ( $a * pow($base, $t) );
        }

        return $out;
    }

Reverse method

    /**
     * anyTodec converter
     * 
     * @param string $num
     * @param string $baseChars
     * @param integer $base
     * @return string
     */
    function anyToDec($num, $baseChars = '', $base = 62, $index = false) {

        $baseChars = empty($baseChars) ? 'HbUlYmGoAd0ScKq6Er5PuZp3OsQCh4RfNMtV8kJiLv9yXeI1aWgFj2zTx7DnBw' : $baseChars;
        if (!$base) {
            $base = strlen($index);
        } else if (!$index) {
            $index = substr($baseChars, 0, $base);
        }
        $out = 0;
        $len = strlen($num) - 1;
        for ($t = 0; $t <= $len; $t++) {
            $out = $out + strpos($index, substr($num, $t, 1)) * pow($base, $len - $t);
        }
        return $out;
    }
Tigran Babajanyan
  • 1,967
  • 1
  • 22
  • 41
1

have an array of characters like:

$chars = array(
    1 => 'a',
    2 => 'b',
    //....
    27 => 'A',
    28 => 'B'
);

function getCharacter($key)
{
    if(array_key_exists($key, $chars[$key]))
        return $chars[$key];
    return false;
}

function getNumber($char)
{
    return array_search($char, $chars);
}
Patrick
  • 3,142
  • 4
  • 31
  • 46
1

If you have gmp extension:

gmp_strval(gmp_init($x, 10), 62)
Vladislav Ross
  • 541
  • 5
  • 6
0
function convertBase10ToBase62($num){
$charset="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$rtn="";

$n=$num;$base=62;
while($n>0){
    $temp=$n%$base;
    $rtn=$charset[$temp].$rtn;
    $n=intval($n/$base);
}
 return $rtn;
}
sfg2k
  • 139
  • 1
  • 10