将字节数据编码为数字

Encoding byte data into digits

提问人:Pekka 提问时间:6/6/2010 最后编辑:Pekka 更新时间:1/23/2011 访问量:6165

问:

有没有一种通用的方法来编码和解码任意数据,以便编码的最终结果仅由数字组成 - 如base64_encode但没有字母?

虚构的例子:

$encoded = numbers_encode("Mary had a little lamb");

echo $encoded; // outputs e.g. 12238433742239423742322 (fictitious result)

$decoded = numbers_decode("12238433742239423742322");

echo $decoded; // outputs "Mary had a little lamb"
PHP 算法 编码

评论

2赞 William Leader 6/6/2010
字符串只是映射到人类可读字符的一组数字。告诉我们更多关于你为什么要做这样的事情,你可能会得到一个很好的答案。是否希望能够将数字转换回原始字符串?如果没有,哈希函数可能就足够了。
1赞 Pekka 6/6/2010
@William在我目前的情况下,我想将一个由数字和字母组成的 16 个字符的 URL 标识符(内部 ID,看起来很丑)转换为“仅数字”表示形式,以使其更直观,用作锚点访问 CMS 中的不同内容块。
1赞 Andy E 6/6/2010
@Pekka:你虚构的结果似乎有点乐观,不是吗?它比原始字符串短一个字符!;-)
3赞 Pekka 6/6/2010
@Andy嘿,我不仅想要编码,还想要压缩!这是不言而喻的。请用四行 PHP 来表达。
2赞 Pekka 6/6/2010
@Joel我是在开玩笑,最初给虚构的数字太少了。

答:

7赞 Will Hartung 6/6/2010 #1

嗯,这将是“Base 8”编码,而不是Base 64。这更广为人知的是 Octal。

Base64 所做的只是将位流转换为 6 位块 (0-63),并从 64 个字符的字符集中分配一个字符。八进制使用 3 位,0-7。因此,它可以使用 ABCDEFGH,但改用 0-7。您不能(轻松)使用 0-9,因为 0-9 最多为 4 位,但不完全是 4 位。这就是使它成为二进制数据糟糕的编码的原因。

评论

0赞 Pekka 6/6/2010
我明白了,为背景欢呼。我需要它来从丑陋(但只有 16 个字符)标识符构建 URL,因此效率方面并不重要。用户贡献的说明中有一个实现:de.php.net/manual/en/function.base64-encode.php#78765 我将尝试让它在 base 8 中工作。
1赞 Nick Johnson 6/6/2010
它不一定是以 8 为基数 - 它同样可以是以 10 为基数。
2赞 el.pescado - нет войне 6/6/2010 #2

非常简单的例子 - 它将每个输入字节表示为 3 位十进制数:

function data2numbers ($data) {
    $out = "";
    for ($i = 0; $i < strlen ($data); $i++) {
        $out .= sprintf ("%03d", ord ($data[$i]));
    }
    return $out;
}

缺点是它使任何输入数据的大小增加三倍(每个输入字节表示为三个输出字节)。

解码功能留给读者作为练习;

评论

0赞 Pekka 6/6/2010
聪明!我早就想过了。它会占用比必要的更多的空间,但它可以满足我的目的。我会拭目以待,看看是否有人本着问题的精神提出了一个真正的“base8”实现:)
3赞 allnightgrocery 6/6/2010 #3

无论您如何编码,您最终都会回到较小的基数。通过一些 dechex() 转换,可以将生成的整数缩小得更小一些,但最终您只会保存几个字符。话虽如此,当您开始用 0-9 表示多字节字符时,这个数字确实会膨胀。

我不得不怀疑,作为 ID、表示单词或完整字符串的整数是否不会提供更小的占用空间。不是真正的直接编码,而是一个可行的选择。

@el.pescado在上半场得到了赞誉,但他确实挑战了读者。所以,我回应了(主要是因为我想了解发生了什么)。

function pekka_encode($s) {
    $out = '';
    for ($i=0;$i<strlen($s); $i++) {
        $out .= sprintf("%03d", ord($s[$i]));     
    }
    return $out;
}

function pekka_decode($s) {
    $out = '';
    for ($i=0;$i<strlen($s);$i+=3) {
        $out .= chr($s[$i].$s[$i+1].$s[$i+2]);
    }
    return $out;
}

评论

0赞 Alix Axel 6/8/2010
+1,对于解码功能:implode('', array_map('chr', str_split($s, 3)));
14赞 Artefacto 8/4/2010 #4

您可以将(单字节字符)字符串视为以 256 为基数的编码数字,其中“\x00”表示 0,“”(空格,即“\x20”)表示 32,依此类推,直到“\xFF”表示 255。

仅使用数字 0-9 的表示可以通过将表示更改为以 10 为基数来完成。

请注意,“base64 编码”实际上并不是基本转换。base64 将输入分成 3 个字节(24 位)的组,并分别对这些组进行基本转换。这很有效,因为一个 24 位的数字可以用 64 基数的四位数字表示 (2^24 = 64^4)。

这或多或少是 el.pescado 所做的——他将输入数据分成 8 位部分,然后将数字转换为以 10 为基数。但是,相对于 base 64 编码,这种技术有一个缺点——它不能与字节边界正确对齐。要用 8 位表示一个数字(无符号时为 0-255),我们需要以 10 为基数的三位数字。但是,最左边的数字比其他数字的信息少。它可以是 0、1 或 2(对于无符号数字)。

以 10 为基数的数字存储 log(10)/log(2) 位。无论您选择哪种块大小,您都永远无法将表示形式与 8 位字节对齐(在我之前的段落中描述的“对齐”意义上)。因此,最紧凑的表示形式是基本转换(您可以将其视为只有一个大块的“基本编码”)。

下面是 bcmath 的示例。

bcscale(0);
function base256ToBase10(string $string) {
    //argument is little-endian
    $result = "0";
    for ($i = strlen($string)-1; $i >= 0; $i--) {
        $result = bcadd($result,
            bcmul(ord($string[$i]), bcpow(256, $i)));
    }
    return $result;
}
function base10ToBase256(string $number) {
    $result = "";
    $n = $number;
    do {
        $remainder = bcmod($n, 256);
        $n = bcdiv($n, 256);
        $result .= chr($remainder);
    } while ($n > 0);

    return $result;
}

$string = "Mary had a little lamb";
$base10 = base256ToBase10($string);
echo $base10,"\n";
$base256 = base10ToBase256($base10);
echo $base256;

我们得到

36826012939234118013885831603834892771924668323094861
Mary had a little lamb

由于每个数字只编码位,因此预计数字往往会长 140%(而不是像 el.pescado 的答案那样长 200%)。log(10)/log(2)=~3.32193

评论

1赞 Pekka 8/4/2010
很棒的东西,这听起来很正确。将测试它并返回。