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

时间:2013-12-21 14:39:38

标签: java string bit-shift

我发现了这个奇怪之处:<​​/ p>

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

输出:

hello world

这是如何运作的?

9 个答案:

答案 0 :(得分:254)

数字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位,可以表示2⁵= 32个字符。英文字母包含26个字母,这留下了32 - 26 = 6个符号的空间 除了信件。通过这种编纂方案,您可以拥有所有26个(一个案例)英文字母和6个符号(其中包含空格)。

算法描述

for循环中的>>= 5从一个组跳到另一个组,然后5位组被隔离并且在句子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

在这里你可以看到我们想要映射的ascii字符以第7和第6位(11xxxxx₂)开头(空格除外,它只有第6位),你可以{{1} 5位 使用OR96)进行编码,这应该足以进行映射,但这对空间无效(空间!)

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

到目前为止,5位组的格式为:l & 31 | 64(空格为10xxxxx₂)。 如果我们可以将空间映射到1011111₂ = 95₁₀而不影响其他值,那么我们可以打开第6位,这应该是全部。 以下是使用mod的0部分,空间为mod 95 操作1011111₂ = 95₁₀只有空格返回(l & 31 | 64) % 95),此后,代码通过添加0将第6位打开 到前一个结果,32₁₀ = 100000₂将5位值转换为有效的ascii字符

((l & 31 | 64) % 95) + 32)

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

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

答案 1 :(得分: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));
}

这是

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

答案 2 :(得分:26)

有趣!

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

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

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

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


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

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

答案 3 :(得分:17)

您收到的结果恰好是char以下值的表示

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

答案 4 :(得分:16)

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

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

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

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

答案 5 :(得分: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));

但出于与此不同的原因:

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));

答案 6 :(得分:2)

如果没有Oracle标记,很难看到这个问题。活跃的赏金把我带到了这里。我希望这个问题还有其他相关的技术标签: - (

我主要使用Oracle database,因此我会使用一些Oracle知识来解释和解释: - )

我们将数字4946144450195624转换为binary。为此,我使用一个名为dec2bin的小function,即十进制到二进制

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-bit转化。从右到左开始分组,每组有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 个字符(包括空格),因此我们需要将 2 位添加到我们剩下的最后一个组中,只有3位分组后。

所以,现在我们有: -

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

现在,我们需要将其转换为7位ascii值。对于字符很容易,我们需要设置第6和第7位。将11添加到左上方的每个5位组中。

这给出了: -

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

让我们解释二进制值,我将使用binary to decimal conversion function

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>

那么,我们在输出中得到了什么?

明白

反向是hello⌂world。唯一的问题是空间。 @higuaro在他的回答中解释了原因。老实说,我在第一次尝试时无法解释空间问题,直到我看到他的答案中给出的解释。

答案 7 :(得分:1)

我发现在翻译成PHP时,代码稍微容易理解,如下所示:

<?php

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

请参阅live code

答案 8 :(得分:0)

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

使它成为上限:3