PHP - 如何使用 base_convert() 进行 62 进制转换

10
我需要一个base_convert()函数,它可以从2进制到62进制进行转换,但我缺少需要使用的数学知识。由于PHP的限制,我知道需要使用bcmath,这是可以接受的。
这样的函数将数字从十进制转换到另一种最高达62进制,但我想要实现与base_convert()相同的功能,例如:只有一个函数可以在任意进制之间进行转换。
我找到了一个似乎可以做到这一点的函数,但它让我感觉有一些冗余和缓慢的代码,如果我知道德语,我想微调一下它,但可惜我不知道 :(
这是该函数的更易读版本:
function bc_base_convert($value, $quellformat, $zielformat)
{
    $vorrat = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

    if (min($quellformat, $zielformat) < 2)
    {
        trigger_error('Bad Format min: 2', E_USER_ERROR);
    }

    if (max($quellformat, $zielformat) > strlen($vorrat))
    {
        trigger_error('Bad Format max: ' . strlen($vorrat), E_USER_ERROR);
    }

    $dezi = '0';
    $level = 0;
    $result = '';
    $value = trim(strval($value), "\r\n\t +");
    $vorzeichen = '-' === $value{0} ? '-' : '';
    $value = ltrim($value, "-0");
    $len = strlen($value);

    for ($i = 0; $i < $len; $i++)
    {
        $wert = strpos($vorrat, $value{$len - 1 - $i});

        if (FALSE === $wert)
        {
            trigger_error('Bad Char in input 1', E_USER_ERROR);
        }

        if ($wert >= $quellformat)
        {
            trigger_error('Bad Char in input 2', E_USER_ERROR);
        }

        $dezi = bcadd($dezi, bcmul(bcpow($quellformat, $i), $wert));
    }

    if (10 == $zielformat)
    {
        return $vorzeichen . $dezi; // abkürzung
    }

    while (1 !== bccomp(bcpow($zielformat, $level++), $dezi));

    for ($i = $level - 2; $i >= 0; $i--)
    {
        $factor = bcpow($zielformat, $i);
        $zahl = bcdiv($dezi, $factor, 0);
        $dezi = bcmod($dezi, $factor);
        $result .= $vorrat{$zahl};
    }

    $result = empty($result) ? '0' : $result;

    return $vorzeichen . $result;
}

有人能为我解释上述函数或给我一些关于任意进制之间直接转换过程的指导吗?

6个回答

16

从PHP 5.3.2开始,bc_math和gmp都支持最大为62的进制,所以你可以这样做:

echo gmp_strval(gmp_init($mynumber, $srcbase), $destbase);

或者使用 bc_math 等效函数。


2
很高兴知道,但是我找不到 BC Math 的等效物。 - Alix Axel
base_convert() 不支持高达 62 进制。 - dresende
不要介意我自己推销一下,我编写了一个使用 GMP 处理任意数字和进制的实用程序,源代码托管在 GitHub:https://github.com/thunderer/Numbase。 - Tomasz Kowalczyk

10
请不要问我从哪里得到的,我只记得它是基于我在网上找到的一些示例...
  function charset_base_convert ($numstring, $fromcharset, $tocharset) {
     $frombase=strlen($fromcharset);
     $tobase=strlen($tocharset);
     $chars = $fromcharset;
     $tostring = $tocharset;

     $length = strlen($numstring);
     $result = '';
     for ($i = 0; $i < $length; $i++) {
         $number[$i] = strpos($chars, $numstring{$i});
     }
     do {
         $divide = 0;
         $newlen = 0;
         for ($i = 0; $i < $length; $i++) {
             $divide = $divide * $frombase + $number[$i];
             if ($divide >= $tobase) {
                 $number[$newlen++] = (int)($divide / $tobase);
                 $divide = $divide % $tobase;
             } elseif ($newlen > 0) {
                 $number[$newlen++] = 0;
             }
         }
         $length = $newlen;
         $result = $tostring{$divide} . $result;
     }
     while ($newlen != 0);
     return $result;
  }

非常有用,可以超越62个字符的限制! - Xeoncross
经过几天的搜索如何将数字字符串转换(因为PHP解析bigint为float并变得不精确),这是一个可以进行反向转换的函数。例如,将$number =“1255276776369394619”转换为b4JlnAoHP37,反之亦然。太棒了! - Avatar

1
任何翻译问题,从数字基础到人类语言,最简单的方法是通过中间格式进行翻译。
function bc_base_convert($num, $from, $to) {
    return bc_convert_to(bc_parse_num($num, $from), $to);
}

现在你只需要编写bc_convert_tobc_parse_num。如果平台区分数字类型,你需要考虑这一点。此外,浮点数需要特别注意,因为一个数字在一个进制下可能有一个有限的表示,但在另一个进制下可能没有(例如1/3是0.13但是0.333...10,而1/1010是.0001100110011...2)。

关于转换的一般性解释,可以考虑 位置基数系统 的工作原理。在基数为 b 的情况下,形如 "anan-1...a1a0" 的数字表示为 "an*bn + an-1*bn-1 + ... + a1*b1 + a0*b0"。转换基本上是通过在另一个基数 β 的上下文中评估表达式来实现的。


抱歉,我不明白,您的意思是我不能直接将基数61转换为基数5吗? - Alix Axel
1
你可以这样做,但使用平台本地格式作为中间形式会更容易。 - outis

1

我在互联网和这个答案中找到的大多数示例都使用了BC Math函数。如果您不想使用BC Math函数,可以看看这个库: http://www.lalit.org/lab/base62-php-convert-number-to-base-62-for-short-urls/

  • 它不使用BC Math函数,因此可以在没有BC Math库的情况下工作。
  • 当基数低于36时,它使用本地的base_convert函数以实现更快的执行速度。
  • 输出数字与本地的base_convert函数向后兼容。
  • 可用于在2-64之间转换任意基数。

0

我在这里写了关于使用BCMath函数进行十进制/二进制转换的文章:http://www.exploringbinary.com/base-conversion-in-php-using-bcmath/。您可以轻松修改该代码以将其转换为不同的基数。

例如,在转换整数的情况下,修改dec2bin_i()和bin2dec_i()例程。重命名它们并添加一个基本参数--类似于dec2base_i($base,$decimal_i)和base2dec_i($base,$num_i),将硬编码的“2”更改为变量$base,将数字余数转换为基数的字符,并重新命名变量。

现在,要在任意基数之间进行转换,请使用十进制作为中间值并调用这两个新函数。例如,通过调用$dec = base2dec_i('42','123'),然后是$b59 = dec2base_i(59,$dec),将基数42的数字“123”转换为基数59。

(您还可以制作一个组合函数,一次完成所有操作。)


1
请注意,base_convert() 本身在内部使用一个中间基数,正如我在这里所指出的:http://www.exploringbinary.com/base-conversion-in-php-using-built-in-functions/ - Rick Regan

-1

如果可能的话,此函数将输出与GNU多精度相同的内容...

<?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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接