这怎么打印"你好世界"?

Boh*_*ian 163 java string bit-shift

我发现这个怪异:

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

输出:

hello world
Run Code Online (Sandbox Code Playgroud)

这是如何运作的?

hig*_*aro 255

该数字4946144450195624适合64位,其二进制表示为:

 10001100100100111110111111110111101100011000010101000
Run Code Online (Sandbox Code Playgroud)

程序从右到左解码每个5位组的字符

 00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
   d  |  l  |  r  |  o  |  w  |     |  o  |  l  |  l  |  e  |  h
Run Code Online (Sandbox Code Playgroud)

5位编码

对于5位,可以表示2⁵= 32个字符.英文字母包含26个字母,除了字母外,还留有32 - 26 = 6个符号的空间.通过这种编纂方案,您可以拥有所有26个(一个案例)英文字母和6个符号(其中包含空格).

算法描述

>>= 5在for循环跳跃组与组的,则该5位组被分离AND运算与掩模的数目31?? = 11111?在句子l & 31

现在,代码将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
Run Code Online (Sandbox Code Playgroud)

在这里你可以看到我们要映射的ascii字符以第7和第6位set(11xxxxx?)开头(空格除外,它只有第6位),你可以OR96(96?? = 1100000?)进行5位编码,那应该是足以进行映射,但这对空间不起作用(darn space!)

现在我们知道必须特别注意与其他角色同时处理空间.为实现此目的,代码使用OR 64 64?? = 1000000?(l & 31 | 64)将提取的5位组的第7位打开(但不是第6位).

到目前为止,5位组的形式是:( 10xxxxx?空间将是1011111? = 95??).如果我们可以将空间映射到不0影响其他值,那么我们可以打开第6位,这应该是全部.这是mod 95部件发挥作用,空间是1011111? = 95??,使用mod操作(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
Run Code Online (Sandbox Code Playgroud)

下面的代码执行逆过程,给定一个小写字符串(最多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);
    }
}    
Run Code Online (Sandbox Code Playgroud)

  • 这个答案毫无疑问.相反,它会让你思考. (11认同)
  • 答案比问题更难:D (6认同)

Jay*_*yan 39

为上述答案添加一些价值.以下groovy脚本打印中间值.

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));
}
Run Code Online (Sandbox Code Playgroud)

这里是

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
Run Code Online (Sandbox Code Playgroud)


Ami*_*deh 26

有趣!

可见的标准ASCII字符的范围为32到127.

这就是你在那里看到32和95(127 - 32)的原因.

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

正长数是63位数,大到足以容纳12个字符的加密形式.所以它足以容纳Hello word,但对于较大的文本,你应该使用更大的数字,甚至是BigInteger.


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

我们将每个UTF-8(16位)字符转换为7位,并获得超过56%的压缩比.因此,我们可以在相同数量的SMS中发送长度为两倍的文本.(这在某种程度上发生了同样的事情).


Vik*_*s V 17

您得到的结果恰好char代表了以下值

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


Ale*_*sky 16

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

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

正如你所说,困难的部分是编码空间.小写英文字母在Unicode(和ascii,以及大多数其他编码)中占据连续范围97-122,但空间为32.

为了克服这个问题,你使用了一些算法.((x+64)%95)+32几乎相同x + 96(注意在这种情况下,按位OR如何相当于加法),但是当x = 31时,我们得到32.


גלע*_*רקן 6

由于类似的原因,它打印"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));
Run Code Online (Sandbox Code Playgroud)

但有一个不同的原因:

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));
Run Code Online (Sandbox Code Playgroud)

  • 你应该解释你在做什么,而不是发布另一个谜语 (18认同)