我有一个很长的“二进制字符串”,比如 PHPs pack 函数的输出。
如何将此值转换为 base62 (0-9a-zA-Z)?内置的数学函数会溢出如此长的输入,并且 BCmath 没有 base_convert 函数或任何特定的函数。我还需要一个匹配的“pack base62”函数。
我有一个很长的“二进制字符串”,比如 PHPs pack 函数的输出。
如何将此值转换为 base62 (0-9a-zA-Z)?内置的数学函数会溢出如此长的输入,并且 BCmath 没有 base_convert 函数或任何特定的函数。我还需要一个匹配的“pack base62”函数。
我认为这个问题背后存在误解。基本转换和编码/解码是不同的。的输出base64_encode(...)
不是一个大的 base64 数。它是一系列离散的 base64 值,对应于压缩函数。这就是为什么 BC Math 不起作用的原因,因为 BC Math 关注的是单个大数,而不是实际上代表二进制数据的小数组的字符串。
这是一个示例来说明差异:
base64_encode(1234) = "MTIzNA=="
base64_convert(1234) = "TS" //if the base64_convert function existed
base64 编码将输入分成 3 个字节组(3*8 = 24 位),然后将每个 6 位子段(2^6 = 64,因此为“base64”)转换为相应的 base64 字符(值为“ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",其中 A = 0,/ = 63)。
在我们的示例中,base64_encode()
将“1234”视为 4 个字符的字符串,而不是整数(因为base64_encode()
不对整数进行操作)。因此它输出“MTIzNA==”,因为(在 US-ASCII/UTF-8/ISO-8859-1 中)“1234”是二进制的 00110001 00110010 00110011 00110100。这分为 001100(十进制 12,字符“M”)010011(十进制 19,字符“T”)001000(“I”)110011(“z”)001101(“N”)00。不完整,它用 0 填充,值为 000000(“A”)。因为一切都是由 3 个输入字符组完成的,所以有 2 个组:“123”和“4”。最后一组用 = 填充以使其长度为 3 个字符,因此整个输出变为“MTIzNA==”。
另一方面,转换为 base64采用单个整数值并将其转换为单个 base64 值。对于我们的示例,如果我们使用与上面相同的 base64 值字符串,则 1234(十进制)是“TS”(base64)。从左到右向后工作:T = 19(第 1 列),S = 18(第 0 列),所以 (19 * 64^1) + (18 * 64^0) = 19 * 64 + 18 = 1234 (十进制)。相同的数字可以用十六进制(base16)表示为“4D2”:(4 * 16^2) + (D * 16^1) + (2 * 16^0) = (4 * 256) + (13 * 16 ) + (2 * 1) = 1234(十进制)。
与encoding不同,它接受字符串并对其进行更改,基本转换不会更改实际数字,只会更改其表示形式。十六进制(base16)“FF”与十进制(base10)“255”是相同的数字,与二进制(base2)中的“11111111”相同。把它想象成货币兑换,如果汇率从未改变:1 美元与 0.79 英镑的价值相同(今天的汇率,但假装它从未改变)。
在计算中,整数通常作为二进制值进行运算(因为很容易构建 1 位算术单元,然后将它们堆叠在一起以形成 32 位/等算术单元)。要做像“255 + 255”(十进制)这样简单的事情,计算机需要先将数字转换为二进制(“11111111”+“11111111”),然后在算术逻辑单元(ALU)中执行运算。
几乎所有其他基数的使用都纯粹是为了方便人类(演示) - 计算机将其内部值 11111111(二进制)显示为 255(十进制),因为人类受过对十进制数进行运算的训练。该函数base64_convert()
不作为标准 PHP 曲目的一部分存在,因为它通常对任何人都没有用:没有多少人本机读取 base64 数字。相比之下,二进制 1 和 0 有时对程序员很有用(我们可以像使用开/关开关一样使用它们!),而十六进制便于人类编辑二进制数据,因为整个 8 位字节可以明确表示为 00 到 FF,不会浪费太多空间。
您可能会问,“如果基础转换只是为了演示,那么为什么存在 BC Math?” 这是一个公平的问题,也是我说“几乎”纯粹是为了演示的原因:典型的计算机仅限于 32 位或 64 位宽的数字,这些数字通常足够大。有时您需要对不适合这些寄存器的非常非常大的数字(例如 RSA 模数)进行操作。BC Math 通过充当抽象层来解决这个问题:它将大量数字转换为长文本字符串。当需要进行一些操作时,BC Math 会煞费苦心地将长长的文本字符串分解成计算机可以处理的小块。它比本机操作慢得多,但它可以处理任意大小的数字。
除非你真的,真的必须有 base62,否则为什么不去:
base64_encode()
base64_decode()
唯一添加的其他字符是“+”和“=”,这是一种非常著名的方法,可以使用许多其他语言中的可用函数来打包和解包二进制字符串。
这是一个base_conv()
可以在完全任意的基数之间转换的函数,表示为字符串数组;每个数组元素代表该基数中的单个“数字”,因此也允许多字符值(避免歧义是您的责任)。
function base_conv($val, &$baseTo, &$baseFrom)
{
return base_arr_to_str(base_conv_arr(base_str_to_arr((string) $val, $baseFrom), count($baseTo), count($baseFrom)), $baseTo);
}
function base_conv_arr($val, $baseToDigits, $baseFromDigits)
{
$valCount = count($val);
$result = array();
do
{
$divide = 0;
$newlen = 0;
for ($i = 0; $i < $valCount; ++$i)
{
$divide = $divide * $baseFromDigits + $val[$i];
if ($divide >= $baseToDigits)
{
$val[$newlen ++] = (int) ($divide / $baseToDigits);
$divide = $divide % $baseToDigits;
}
else if ($newlen > 0)
{
$val[$newlen ++] = 0;
}
}
$valCount = $newlen;
array_unshift($result, $divide);
}
while ($newlen != 0);
return $result;
}
function base_arr_to_str($arr, &$base)
{
$str = '';
foreach ($arr as $digit)
{
$str .= $base[$digit];
}
return $str;
}
function base_str_to_arr($str, &$base)
{
$arr = array();
while ($str === '0' || !empty($str))
{
foreach ($base as $index => $digit)
{
if (mb_substr($str, 0, $digitLen = mb_strlen($digit)) === $digit)
{
$arr[] = $index;
$str = mb_substr($str, $digitLen);
continue 2;
}
}
throw new Exception();
}
return $arr;
}
例子:
$baseDec = str_split('0123456789');
$baseHex = str_split('0123456789abcdef');
echo base_conv(255, $baseHex, $baseDec); // ff
echo base_conv('ff', $baseDec, $baseHex); // 255
// multi-character base:
$baseHelloworld = array('hello ', 'world ');
echo base_conv(37, $baseHelloworld, $baseDec); // world hello hello world hello world
echo base_conv('world hello hello world hello world ', $baseDec, $baseHelloworld); // 37
// ambiguous base:
// don't do this! base_str_to_arr() won't know how to decode e.g. '11111'
// (well it does, but the result might not be what you'd expect;
// It matches digits sequentially so '11111' would be array(0, 0, 1)
// here (matched as '11', '11', '1' since they come first in the array))
$baseAmbiguous = array('11', '1', '111');