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


163

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

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

Вывод:

hello world

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


14
Я имею в виду, вы можете понять это самостоятельно.
Сотириос Делиманолис

30
Да. Я признаю ... я ловлю шляпы :)
Богемный

6
Я думаю, что видел этот вопрос, заданный здесь раньше ..
Zavior

6
@ Оли. Для этого должна быть шляпа.
Сотириос Делиманолис

12
Подобные вопросы, которые не улучшают базу данных, а существуют исключительно как клик-приманки, являются верным способом отменить игру Hat в будущем. Пожалуйста, не портите игру, желая этого.
Blazemonger

Ответы:


256

Число 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 происходит переход от группы к группе, затем 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-го бита set ( 11xxxxx₂) (кроме пробела, в котором включен только 6-й бит), вы можете OR5-битную кодификацию с 96( 96₁₀ = 1100000₂), и это должно быть достаточно, чтобы сделать отображение, но это не сработает для пространства (черт побери!)

Теперь мы знаем, что особое внимание нужно уделить обработке пространства одновременно с другими персонажами. Для этого код включает 7-й бит (но не 6-й) в извлеченной 5-битной группе с помощью ИЛИ 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);
    }
}    

11
Этот ответ не оставляет загадок. Скорее, это делает ваше мышление за вас.

7
ответ еще сложнее, чем вопрос: D
Yazan

1
Объяснение намного чище :)
Прашант

40

Добавление некоторой ценности к ответам выше. Следующий скриптовый скрипт печатает промежуточные значения.

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


В коде OP происходит намного больше. Например, это не совсем объясняет, что | 64делает.
Тед Хопп

1
@ Амир: на самом деле 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

Он печатает «привет мир» по той же причине, что и делает:

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
Вы должны объяснить, что вы делаете, вместо того, чтобы публиковать еще одну загадку
Александр Дубинский

1
Я предлагаю вам приложить некоторые усилия, чтобы найти сайт (возможно, Beta StackExchange?), Где приветствуются веселые загадки. Stack Overflow - это сайт вопросов и ответов со строгим контролем.
Марко Топольник

1
@MarkoTopolnik Я бы не хотел жить в мире, где все правила или акценты строго соблюдались, чтобы никогда не допускать никаких исключений. Не говоря уже о том, что на SO существует множество подобных исключений.
גלעד ברקן

1
Я бы тоже, но ТАК такой мир необычайно велик. Конечно, есть исключения даже здесь, но они не приветствуются .
Марко Топольник

1
Еще 15 поделились мнением Александра. И вы правы, указывая на то, что сам вопрос не подходит для SO, как прокомментировано ниже.
Марко Топольник

3

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

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

Давайте преобразовать число 4946144450195624в binary. Для этого я использую маленький functiondec2bin, то есть десятичный в двоичный .

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>

Итак, что мы получаем в результате?

Dlrow ⌂ Olleh

Это привет мир в обратном порядке. Единственная проблема - это пространство . И причина хорошо объяснена @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 ((символ) (((l & 31 | 64)% 95) + 32/1002439 * 1002439));

Чтобы сделать это заглавными буквами: 3


1
подумайте о том, чтобы добавить объяснение, что вы делаете и почему.
Федорки 'ТАК прекрати вредить'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.