Как это печатает «Привет мир»?

161

Я обнаружил эту странность:

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

Вывод:

hello world

Как это работает?

  • 13
    Я имею в виду, вы можете понять это самостоятельно.
  • 28
    Да. Признаюсь ... я ловлю шляпы :)
Показать ещё 28 комментариев
Теги:
string
bit-shift

9 ответов

249
Лучший ответ

Число 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 в циклах for-loop переходы из группы в группу, тогда группа из 5 бит выделяется ANDing числом с маской 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-й бит), вы могли бы OR немного кодирования с 96 (96₁₀ = 1100000₂), и этого должно быть достаточно, чтобы сделать сопоставление, но это не сработало бы для пространства (darn space!)

Теперь мы знаем, что необходимо уделять особое внимание процессу пространства одновременно с другими персонажами. Для этого код превращает 7-й бит (но не 6-й) в извлеченная 5-битная группа с OR 64 64₁₀ = 1000000₂ (l & 31 | 64).

Пока 5-битная группа имеет вид: 10xxxxx₂ (пробел будет 1011111₂ = 95₁₀). Если мы можем сопоставить пространство с 0, не затрагивая другие значения, тогда мы можем включить 6-й бит, и это должно быть все. Вот что входит в игру mod 95, пробел - 1011111₂ = 95₁₀, с использованием мода операция (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 символов), возвращает значение длиной 64 бит, которое может использоваться с кодом OP:

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);
    }
}    
  • 1
    Я закодировал его в int, 3 бита на символ.
  • 11
    Этот ответ не оставляет загадок. Скорее, это делает ваше мышление для вас.
Показать ещё 2 комментария
40

Добавление значения к приведенным выше ответам. После groovy script печатает промежуточные значения.

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
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-сообщений. (Это как-то то же самое произошло здесь).

  • 0
    В коде OP происходит намного больше. Например, это не совсем объясняет, что | 64 делает.
  • 1
    @Amir: Амир: на самом деле 95 там, потому что вам нужно получить пробел.
17

Вы получаете результат, который представляет собой char представление ниже значений

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

Вы кодировали символы как 5-битные значения и упаковали 11 из них в 64-разрядную длину.

(packedValues >> 5*i) & 31 является i-м кодированным значением с диапазоном 0-31.

Жесткая часть, как вы говорите, кодирует пространство. В нижнем регистре английские буквы занимают смежный диапазон 97-122 в Unicode (и ascii, и большинство других кодировок), но пространство равно 32.

Чтобы преодолеть это, вы использовали некоторую арифметику. ((x+64)%95)+32 почти совпадает с x + 96 (обратите внимание, как в этом случае побитовое ИЛИ эквивалентно добавлению), но когда 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));

но несколько иначе, чем это:

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));
  • 18
    Вы должны объяснить, что вы делаете, вместо того, чтобы публиковать другую загадку
  • 0
    @AleksandrDubinsky Я так не думаю. Во-первых, то, что я делаю, было описано в других ответах на этой странице. А во-вторых, я считаю, что некоторым людям может быть полезно и полезно учиться, думая и выясняя это для себя.
Показать ещё 11 комментариев
2

Без тега Oracle было трудно понять этот вопрос. Активная щедрость привела меня сюда. Я хочу, чтобы у этого вопроса были другие соответствующие теги технологии: - (

В основном я работаю с Oracle database, поэтому я бы использовал некоторые знания Oracle для интерпретации и объяснения: -)

Позвольте преобразовать число 4946144450195624 в binary. Для этого я использую небольшой function, называемый dec2bin i.e. десятичной-двоичной.

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>

Теперь catch является преобразованием 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 total имеет 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>

Итак, что мы получаем на выходе?

d l r o w ⌂ o l l h h

Это hello⌂world в обратном порядке. Единственная проблема - это space. И причина хорошо объясняется @higuaro в его ответе. Я, честно говоря, не мог сам интерпретировать космическую проблему с первой попытки, пока не увидел объяснение, данное в его ответе.

1

Я нашел код немного легче понять при переводе на PHP следующим образом:

<?php

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

Смотрите живой код

0

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

Чтобы сделать это: 3

  • 1
    подумайте о том, чтобы добавить объяснение, что вы делаете и почему.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню