1. ホーム
  2. java

[解決済み] hello world "と表示されるのはなぜですか?

2022-04-23 21:30:36

質問

こんな変なものを発見しました。

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

出力します。

hello world

どのように動作するのでしょうか?

どのように解決するのですか?

番号 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個の記号(その間にスペースがある)をすべて持つことができます。

アルゴリズムの説明

>>= 5 はグループからグループへジャンプし、5ビットのグループが分離され、その番号とマスク 31₁₀ = 11111₂ 文中 l & 31

ここで、コードは5ビットの値を対応する7ビットのアスキー文字にマッピングします。ここが難しいところですが、小文字の2進数表現を確認してください。 のアルファベットの文字があります。

  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

ここで、マッピングしたいアスキー文字は、7番目と6番目のビットセットで始まっていることがわかります ( 11xxxxx₂ ) (6ビット目しかオンになっていないスペースを除く)であれば、次のようになります。 OR 5ビット でのコード化 96 ( 96₁₀ = 1100000₂ しかし、これではスペースに対応できません(スペースがない!)。

さて、スペースを他の文字と同時に処理するためには、特別な注意が必要であることがわかりました。そのために、このコードでは、7番目のビットをオンにして(6番目はオンにしていない)、他の文字を処理します。 抽出された5ビット群をOR64で表現したもの 64₁₀ = 1000000₂ ( l & 31 | 64 ).

ここまでが5ビット群の形です。 10xxxxx₂ (スペースは 1011111₂ = 95₁₀ ). もし、空間を 0 他の値に影響を与えないように、6番目のビットをオンにすれば、それで済むはずです。 以下は mod 95 の部分は、スペースが 1011111₂ = 95₁₀ を使用すると、MOD 操作 (l & 31 | 64) % 95) に戻るのはスペースだけです。 0 を追加し、この後、6ビット目をオンにするコードです。 32₁₀ = 100000₂ を前の結果に追加します。 ((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);
    }
}