背景

业务上存在缩短ID长度的诉求,想到可以考虑把整型转成36进制的方法。于是去网上搜索转36进制的实现代码,通过百度搜到了几个实现都不是很好(有的换算直接就是错的),然后换谷歌搜索居然搜到了自己多年前收藏的一份代码(自己完全忘记了做过这个事情)。只是之前的实现只支持32位,64位整型计算会出现误差,于是我又优化了一下这份实现,通过bcmath扩展库来支持64位整型的转换。

结果

10/36进制转换,支持64位的计算(需要bcmath扩展库的支持)

if (!function_exists('base36_encode')) {
    /**
     * 十进制数转换成三十六进制数
     * @param (int)$num : 十进制数
     * return (string) :三十六进制数
    */
    function base36_encode($num) 
    {
        $num = intval($num);
        if ($num < 0)
        {
            return false;
        }
        $charArr = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
        $char = '';
        do
        {
            $key = bcmod($num , 36);
            $char= $charArr[$key] . $char;
            $num = bcdiv(($num - $key),36, 0);
        }
        while ($num > 0);
        return $char;
    }
}

if (!function_exists('base36_decode')) {
    /**
     * 三十六进制数转换成十进制数
     * @param (string)$char :三十六进制数
     * return (int) :十进制数
     */
    function base36_decode($char){
        $charArr = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
        $len = strlen($char);
        $sum = 0;
        for($i=0; $i<$len; ++$i)
        {
            $index = array_search( $char[$i], $charArr);
            $sum = bcadd($sum, bcmul($index, bcpow(36, $len-$i-1, 0)));
        }
        return $sum;
    }
}

以下是单元测试用例(需要phpunit的支持)

class Base36Test extends \PHPUnit\Framework\TestCase
{

    /**
     * base36 en/decode test
     * @test
     */
    public function testBase36()
    {
        $tests =array(
            array('i' => 0, 'c' => '0'),
            array('i' => 9, 'c' => '9'),
            array('i' => 10, 'c' => 'a'),
            array('i' => 15, 'c' => 'f'),
            array('i' => 35, 'c' => 'z'),
            array('i' => 370, 'c' => 'aa'),
            array('i' => 1295, 'c' => 'zz'),
            array('i' => 13330, 'c' => 'aaa'),
            array('i' => 46655, 'c' => 'zzz'),
            array('i' => 479890, 'c' => 'aaaa'),
            array('i' => 1679615, 'c' => 'zzzz'),
            array('i' => 17276050, 'c' => 'aaaaa'),
            array('i' => 60466175, 'c' => 'zzzzz'),
            array('i' => 621937810, 'c' => 'aaaaaa'),
            array('i' => 2147483575, 'c' => 'zik0xj'),
            array('i' => 2147483575, 'c' => 'ZIK0XJ'),
        );

        // for 64bit
        if (8 == PHP_INT_SIZE){
            $tests64 = array(
                array('i' => 2147483576, 'c' => 'zik0xk'),
                array('i' => 4294967295, 'c' => '1z141z3'),
                array('i' => PHP_INT_MAX, 'c' => '1y2p0ij32e8e7'),
                array('i' => PHP_INT_MAX, 'c' => '1Y2P0IJ32E8E7'),
            );
            $tests = array_merge($tests, $tests64);
        }

        // printf("\nPHP_INT_SIZE: %d\n", PHP_INT_SIZE);
        echo "\n";
        foreach($tests as $test){
            printf("=== RUN %s::%s %d,%s\n", __CLASS__, __FUNCTION__, $test['i'], $test['c']);
            $c = base36_encode($test['i']);
            $i = base36_decode($c);
            $this->assertEquals($test['i'], $i);
            $this->assertEquals(strtolower($test['c']), $c);

            $i = base36_decode($test['c']);
            $c = base36_encode($i);
            $this->assertEquals(strtolower($test['c']), $c);
            $this->assertEquals($test['i'], $i);
            printf("--- PASS %d,%s\n", $test['i'], $test['c']);
        }
    }


}