PHP の pack 関数の出力のような長い「バイナリ文字列」があります。
この値を base62 (0-9a-zA-Z) に変換するにはどうすればよいですか? 組み込みの数学関数は、このような長い入力でオーバーフローし、BCmath には base_convert 関数やその特定のものはありません。一致する「pack base62」関数も必要です。
PHP の pack 関数の出力のような長い「バイナリ文字列」があります。
この値を base62 (0-9a-zA-Z) に変換するにはどうすればよいですか? 組み込みの数学関数は、このような長い入力でオーバーフローし、BCmath には base_convert 関数やその特定のものはありません。一致する「pack base62」関数も必要です。
この質問の背後には誤解があると思います。基数変換とエンコード/デコードは異なります。の出力は大きな base64 数値でbase64_encode(...)
はありません。これは、圧縮関数に対応する一連の個別の 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()
を操作しないため)。したがって、(US-ASCII/UTF-8/ISO-8859-1 では) "1234" はバイナリで 00110001 00110010 00110011 00110100 であるため、"MTIzNA==" を出力します。これは、001100 (10 進数で 12、文字 "M") 010011 (10 進数で 19、文字 "T") 001000 ("I") 110011 ("z") 001101 ("N") 00 に分割されます。完全ではなく、0 が埋め込まれ、値は 000000 ("A") になります。すべて入力文字3文字のグループで行うため、「123」と「4」の2グループになります。最後のグループは = でパディングされて 3 文字の長さになるため、出力全体は "MTIzNA==" になります。
一方、base64への変換は、単一の整数値を取り、それを単一の base64 値に変換します。この例では、上記と同じ base64 値の文字列を使用する場合、1234 (10 進数) は "TS" (base64) です。逆方向に左から右へ: T = 19 (列 1)、S = 18 (列 0)、したがって (19 * 64^1) + (18 * 64^0) = 19 * 64 + 18 = 1234 (10 進数)。同じ数値を 16 進数 (base16) で「4D2」と表すことができます: (4 * 16^2) + (D * 16^1) + (2 * 16^0) = (4 * 256) + (13 * 16 ) + (2 * 1) = 1234 (10 進数)。
文字列を取得して変更するencodingとは異なり、基数変換は実際の数値を変更せず、表示を変更するだけです。16 進数 (base16) の "FF" は10 進数 (base10) の "255" と同じ数値で、2 進数 (base2) の "11111111" と同じ数値です。為替レートがまったく変わらない場合の通貨交換のようなものと考えてください。1 米ドルは 0.79 ポンドと同じ値です (今日の為替レートですが、まったく変わらないと仮定します)。
コンピューティングでは、整数は通常、バイナリ値として操作されます (1 ビットの算術ユニットを構築し、それらを積み重ねて 32 ビットなどの算術ユニットを作成するのは簡単だからです)。「255 + 255」(10 進数) のような単純なことを行うには、コンピューターはまず数値を 2 進数 (「11111111」 + 「11111111」) に変換してから、算術論理演算装置 (ALU) で演算を実行する必要があります。
基数の他のほとんどすべての使用法は、純粋に人間の便宜 (表示) のためのものです。人間は 10 進数で操作するように訓練されているため、コンピューターは内部値 11111111 (2 進数) を 255 (10 進数) として表示します。この関数base64_convert()
は、PHP の標準レパートリーの一部として存在しません。これは、多くの場合、誰にとっても有用ではないためです。base64 数値をネイティブに読み取る人間は多くありません。対照的に、2 進数の 1 と 0 はプログラマにとって便利な場合があり (オン/オフ スイッチのように使用できます!)、8 ビット バイト全体を 00 から FF として明確に表すことができるため、バイナリ データを編集する人間にとっては 16 進数が便利です。スペースを無駄にしません。
「基数変換が表示のためだけのものなら、なぜ BC Math が存在するのですか?」と疑問に思うかもしれません。それは公正な質問です。また、純粋にプレゼンテーションのために「ほぼ」と言ったのもまさにその理由です。典型的なコンピューターは、通常十分な大きさの 32 ビットまたは 64 ビット幅の数値に制限されています。場合によっては、これらのレジスターに収まらない非常に大きな数値 (RSA 係数など)を操作する必要がある場合があります。BC Math は、抽象化レイヤーとして機能することでこの問題を解決します。巨大な数を長いテキスト文字列に変換します。何らかの操作を行うとき、BC Math は長い文字列のテキストをコンピューターが処理できる小さなチャンクに細心の注意を払って分割します。ネイティブ操作よりもはるかに遅いですが、任意のサイズの数値を処理できます。
本当にbase62が必要な場合を除き、次の方法を試してみてください。
base64_encode()
base64_decode()
他に追加される文字は「+」と「=」だけで、他の多くの言語で利用可能な関数を使用してバイナリ文字列をパックおよびアンパックするための非常によく知られた方法です。
base_conv()
これは、文字列の配列として表現された、完全に任意の基数の間で変換できる関数です。各配列要素はその基数の 1 つの「数字」を表すため、複数文字の値も使用できます (あいまいさを避けるのはユーザーの責任です)。
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');