【问题标题】:Encoding byte data into digits将字节数据编码为数字
【发布时间】:2011-02-28 06:06:19
【问题描述】:

是否有一种通用的方法来编码和解码任意数据,因此编码的最终结果仅包含数字 - 例如 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"

【问题讨论】:

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

标签: php algorithm encoding


【解决方案1】:

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

只有数字 0-9 的表示可以简单地通过将表示更改为基数 10 来完成。

请注意,“base64 编码”实际上不是 base conversion。 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 玛丽有只小羊羔

由于每个数字仅编码 log(10)/log(2)=~3.32193 位,因此该数字倾向于为 140% longer(而不是 200% 长,就像 el.pescado 的答案那样)。

【讨论】:

  • 好东西,这听起来完全正确。将对其进行测试并返回。
【解决方案2】:

嗯,那将是“base 8”编码而不是 Base 64。这更好地称为八进制。

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

【讨论】:

  • 我明白了,为背景欢呼。我需要它来从难看的(但只有 16 个字符)标识符构建 URL,因此效率方面并不重要。用户贡献的注释中有一个实现:de.php.net/manual/en/function.base64-encode.php#78765 我会尝试让它在 base 8 中工作。
  • 它不必是基数 8 - 它同样可以是基数 10。
【解决方案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;
}

【讨论】:

  • +1,解码功能:implode('', array_map('chr', str_split($s, 3)));
【解决方案4】:

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

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

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

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

【讨论】:

  • 聪明!我曾想过。它占用比必要更多的空间,但它可以满足我的目的。不过,我会等待,看看是否有人本着问题的精神提出了一个真正的“base8”实现:)
猜你喜欢
  • 2020-07-05
  • 2014-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-15
  • 1970-01-01
  • 1970-01-01
  • 2020-05-22
相关资源
最近更新 更多