这个怎么印“你好世界”?

我发现了一个奇怪的现象:

for (long l = 4946144450195624l; l > 0; l >>= 5)
System.out.print((char) (((l & 31 | 64) % 95) + 32));

产出:

hello world

这是怎么回事?

10449 次浏览

您得到的结果恰好是以下值的 char表示形式

104 -> h
101 -> e
108 -> l
108 -> l
111 -> o
32  -> (space)
119 -> w
111 -> o
114 -> r
108 -> l
100 -> d

有意思!

可见的标准 ASCII 字符在32到127之间。

这就是为什么你会看到32和95(127-32)。

事实上,每个字符在这里被映射到5位(你可以找到每个字符的5位组合) ,然后所有的位被连接起来形成一个大数字。

正长是63位数字,大到足以保存12个字符的加密形式。因此,它的大小足以容纳 Hello word,但是对于较大的文本,您应该使用较大的数字,甚至 BigInteger。


在一个应用程序中,我们希望通过短信传输可见的英文字符,波斯文字符和符号。正如您所看到的,有 32 (number of Persian characters) + 95 (number of English characters and standard visible symbols) = 127可能的值,它可以用7位来表示。

我们将每个 UTF-8(16位)字符转换为7位,获得了超过56% 的压缩比。所以我们可以在相同数量的短信中发送两倍长度的短信。(不知何故,这里也发生了同样的事情。)

下面的 好极了脚本打印中间值。

String getBits(long l) {
return Long.toBinaryString(l).padLeft(8, '0');
}


for (long l = 4946144450195624l; l > 0; l >>= 5) {
println ''
print String.valueOf(l).toString().padLeft(16, '0')
print '|' + getBits((l & 31))
print '|' + getBits(((l & 31 | 64)))
print '|' + getBits(((l & 31 | 64) % 95))
print '|' + getBits(((l & 31 | 64) % 95 + 32))


print '|';
System.out.print((char) (((l & 31 | 64) % 95) + 32));
}

这就是:

4946144450195624|00001000|01001000|01001000|01101000|h
0154567014068613|00000101|01000101|01000101|01100101|e
0004830219189644|00001100|01001100|01001100|01101100|l
0000150944349676|00001100|01001100|01001100|01101100|l
0000004717010927|00001111|01001111|01001111|01101111|o
0000000147406591|00011111|01011111|00000000|00100000|
0000000004606455|00010111|01010111|01010111|01110111|w
0000000000143951|00001111|01001111|01001111|01101111|o
0000000000004498|00010010|01010010|01010010|01110010|r
0000000000000140|00001100|01001100|01001100|01101100|l
0000000000000004|00000100|01000100|01000100|01100100|d

您已经将字符编码为5位值,并将其中的11个字符打包为64位 很长

(packedValues >> 5*i) & 31是第 i 个编码的值,范围为0-31。

正如你所说,最难的部分是对空间进行编码。小写英文字母在 Unicode (以及 ASCII和大多数其他编码)中占据了97-122的连续范围,但是空格是32。

为了克服这个问题,您使用了一些算术。((x+64)%95)+32几乎与 x + 96相同(注意在这种情况下,按位 或者等价于加法) ,但是当 x = 31时,我们得到 32

数字 4946144450195624符合64位,它的二进制表示是:

 10001100100100111110111111110111101100011000010101000

该程序从右到左为每个5位组解码一个字符

 00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
d  |  l  |  r  |  o  |  w  |     |  o  |  l  |  l  |  e  |  h

5位编码

对于5位,可以表示25 = 32个字符。这个英文字母包含26个字母,这就为32-26 = 6个符号留出了空间 除了信件。使用这种编码方案,您可以拥有全部26个(一个大小写)英文字母和6个符号(其中包括空格)。

算法描述

为了循环中的 >>= 5从一个组跳转到另一个组,然后5位组得到隔离,并在句子 l & 31中使用掩码 31₁₀ = 11111₂对数字进行 ANDing。

现在,代码将5位值映射到相应的7位 ASCII 字符。这是最棘手的部分。检查小写字母的二进制表示形式 下表中的字母:

  ASCII   |     ASCII     |    ASCII     |    Algorithm
character | decimal value | binary value | 5-bit codification
--------------------------------------------------------------
space   |       32      |   0100000    |      11111
a     |       97      |   1100001    |      00001
b     |       98      |   1100010    |      00010
c     |       99      |   1100011    |      00011
d     |      100      |   1100100    |      00100
e     |      101      |   1100101    |      00101
f     |      102      |   1100110    |      00110
g     |      103      |   1100111    |      00111
h     |      104      |   1101000    |      01000
i     |      105      |   1101001    |      01001
j     |      106      |   1101010    |      01010
k     |      107      |   1101011    |      01011
l     |      108      |   1101100    |      01100
m     |      109      |   1101101    |      01101
n     |      110      |   1101110    |      01110
o     |      111      |   1101111    |      01111
p     |      112      |   1110000    |      10000
q     |      113      |   1110001    |      10001
r     |      114      |   1110010    |      10010
s     |      115      |   1110011    |      10011
t     |      116      |   1110100    |      10100
u     |      117      |   1110101    |      10101
v     |      118      |   1110110    |      10110
w     |      119      |   1110111    |      10111
x     |      120      |   1111000    |      11000
y     |      121      |   1111001    |      11001
z     |      122      |   1111010    |      11010

这里您可以看到我们要映射的 ASCII 字符以第7和第6位集(11xxxxx₂)开始(空间除外,它只开启第6位)。你可以 OR的5位 使用 96(96₁₀ = 1100000₂)进行编码,这应该足以完成映射,但是对于空间(该死的空间!)来说就不行了.

现在我们知道,必须特别注意在处理空间的同时,其他字符。为了实现这个目标,代码在提取的5位组上以 OR 64 64₁₀ = 1000000₂(l & 31 | 64)打开第7位(但不是第6位)。

到目前为止,5位组的形式是: 10xxxxx₂(空间为 1011111₂ = 95₁₀)。

如果我们可以映射空间到 0不影响其他值,然后我们可以打开第6位,这应该是所有。

下面是 mod 95部分的内容,空格是 1011111₂ = 95₁₀,使用 < em > 模 (l & 31 | 64) % 95)行动。只有空格返回到 0,在此之后,代码通过添加 32₁₀ = 100000₂打开第6位 转换为前一个结果 ((l & 31 | 64) % 95) + 32),将5位值转换为有效的 ASCII 字符。

isolates 5 bits --+          +---- takes 'space' (and only 'space') back to 0
|          |
v          v
(l & 31 | 64) % 95) + 32
^           ^
turns the       |           |
7th bit on ------+           +--- turns the 6th bit on

下面的代码执行反向处理,给定一个小写字符串(最多12个字符) ,返回可以与 OP 代码一起使用的64位长值:

public class D {
public static void main(String... args) {
String v = "hello test";
int len = Math.min(12, v.length());
long res = 0L;
for (int i = 0; i < len; i++) {
long c = (long) v.charAt(i) & 31;
res |= ((((31 - c) / 31) * 31) | c) << 5 * i;
}
System.out.println(res);
}
}

它打印“ hello world”的原因与此类似:

for (int k=1587463874; k>0; k>>=3)
System.out.print((char) (100 + Math.pow(2,2*(((k&7^1)-1)>>3 + 1) + (k&7&3)) + 10*((k&7)>>2) + (((k&7)-7)>>3) + 1 - ((-(k&7^5)>>3) + 1)*80));

但出于一个与此不同的原因:

for (int k=2011378; k>0; k>>=2)
System.out.print((char) (110 + Math.pow(2,2*(((k^1)-1)>>21 + 1) + (k&3)) - ((k&8192)/8192 + 7.9*(-(k^1964)>>21) - .1*(-((k&35)^35)>>21) + .3*(-((k&120)^120)>>21) + (-((k|7)^7)>>21) + 9.1)*10));

使用

out.println((char) (((l & 31 | 64) % 95) + 32 / 1002439 * 1002439));

使其资本化。

我主要使用 甲骨文数据库,所以我会用一些 神使的知识来解释: -)

让我们把数字 494614450195624转换成 二进制。为此,我使用一个叫做 dec2bin 的小型 功能,也就是十进制到二进制。

SQL> CREATE OR REPLACE FUNCTION dec2bin (N in number) RETURN varchar2 IS
2    binval varchar2(64);
3    N2     number := N;
4  BEGIN
5    while ( N2 > 0 ) loop
6       binval := mod(N2, 2) || binval;
7       N2 := trunc( N2 / 2 );
8    end loop;
9    return binval;
10  END dec2bin;
11  /


Function created.


SQL> show errors
No errors.
SQL>

让我们使用这个函数来获取二进制值-

SQL> SELECT dec2bin(4946144450195624) FROM dual;


DEC2BIN(4946144450195624)
--------------------------------------------------------------------------------
10001100100100111110111111110111101100011000010101000


SQL>

现在的问题是 5位转换。从右到左开始分组,每组5个数字。我们得到:

100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000

我们将最终留下只有 3数字在最后的权利。因为在二进制转换中,我们总共有53位数字。

SQL> SELECT LENGTH(dec2bin(4946144450195624)) FROM dual;


LENGTH(DEC2BIN(4946144450195624))
---------------------------------
53


SQL>

Hello world 总共有 11字符(包括空格) ,所以我们需要将 位添加到最后一组中,在分组后只剩下3位。

所以,现在我们有:

00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000

现在,我们需要将其转换为7位 ASCII 值。对于字符来说,这很容易; 我们只需要设置第6位和第7位。向左上方的每个5位组添加 11

这意味着:

1100100|1101100|1110010|1101111|1110111|1111111|1101111|1101100|1101100|1100101|1101000

让我们来解释二进制值。

SQL> CREATE OR REPLACE FUNCTION bin2dec (binval in char) RETURN number IS
2    i                 number;
3    digits            number;
4    result            number := 0;
5    current_digit     char(1);
6    current_digit_dec number;
7  BEGIN
8    digits := length(binval);
9    for i in 1..digits loop
10       current_digit := SUBSTR(binval, i, 1);
11       current_digit_dec := to_number(current_digit);
12       result := (result * 2) + current_digit_dec;
13    end loop;
14    return result;
15  END bin2dec;
16  /


Function created.


SQL> show errors;
No errors.
SQL>

让我们看看每个二进制值-

SQL> set linesize 1000
SQL>
SQL> SELECT bin2dec('1100100') val,
2    bin2dec('1101100') val,
3    bin2dec('1110010') val,
4    bin2dec('1101111') val,
5    bin2dec('1110111') val,
6    bin2dec('1111111') val,
7    bin2dec('1101111') val,
8    bin2dec('1101100') val,
9    bin2dec('1101100') val,
10    bin2dec('1100101') val,
11    bin2dec('1101000') val
12  FROM dual;


VAL        VAL        VAL        VAL        VAL        VAL        VAL        VAL        VAL     VAL           VAL
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
100        108        114        111        119        127        111        108        108     101           104


SQL>

让我们看看他们是什么样的人物:

SQL> SELECT chr(bin2dec('1100100')) character,
2    chr(bin2dec('1101100')) character,
3    chr(bin2dec('1110010')) character,
4    chr(bin2dec('1101111')) character,
5    chr(bin2dec('1110111')) character,
6    chr(bin2dec('1111111')) character,
7    chr(bin2dec('1101111')) character,
8    chr(bin2dec('1101100')) character,
9    chr(bin2dec('1101100')) character,
10    chr(bin2dec('1100101')) character,
11    chr(bin2dec('1101000')) character
12  FROM dual;


CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER
--------- --------- --------- --------- --------- --------- --------- --------- --------- --------- ---------
d         l         r         o         w         ⌂         o         l         l         e         h


SQL>

那么,我们得到的输出是什么?

我不知道

这是 你好,世界倒档。唯一的问题是 空间。Higuaro 在他的回答中很好地解释了原因。我真的不能解释空间问题自己第一次尝试,直到我看到他的答案给出的解释。

我发现将这些代码翻译成 PHP 之后稍微容易理解一些,如下所示:

<?php


$result=0;
$bignum = 4946144450195624;
for (; $bignum > 0; $bignum >>= 5){
$result = (( $bignum & 31 | 64) % 95) + 32;
echo chr($result);
}

参见 实时代码