Descubrí esta rareza:
for (long l = 4946144450195624l; l > 0; l >>= 5)
System.out.print((char) (((l & 31 | 64) % 95) + 32));
Salida:
hello world
¿Como funciona esto?
Descubrí esta rareza:
for (long l = 4946144450195624l; l > 0; l >>= 5)
System.out.print((char) (((l & 31 | 64) % 95) + 32));
Salida:
hello world
¿Como funciona esto?
Respuestas:
El número se 4946144450195624
ajusta a 64 bits, su representación binaria es:
10001100100100111110111111110111101100011000010101000
El programa decodifica un carácter para cada grupo de 5 bits, de derecha a izquierda
00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
d | l | r | o | w | | o | l | l | e | h
Para 5 bits, es posible representar 2⁵ = 32 caracteres. El alfabeto inglés contiene 26 letras, esto deja espacio para 32 - 26 = 6 símbolos aparte de las letras. Con este esquema de codificación, puede tener las 26 letras inglesas (un caso) y 6 símbolos (siendo el espacio entre ellos).
En >>= 5
el bucle for salta de un grupo a otro, luego el grupo de 5 bits se aísla ANDing el número con la máscara 31₁₀ = 11111₂
en la oraciónl & 31
Ahora el código asigna el valor de 5 bits a su correspondiente carácter ascii de 7 bits. Esta es la parte difícil, verifique las representaciones binarias para las letras minúsculas del alfabeto en la siguiente tabla:
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
Aquí puede ver que los caracteres ascii que queremos mapear comienzan con el conjunto de 7 y 6 bits ( 11xxxxx₂
) (excepto el espacio, que solo tiene el 6to bit activado ), puede OR
codificar con 5 bits con 96
(96₁₀ = 1100000₂
) y eso debería ser suficiente para hacer el mapeo, pero eso no funcionaría para el espacio (¡maldito espacio!)
Ahora sabemos que se debe tener especial cuidado al procesar el espacio al mismo tiempo que los otros personajes. Para lograr esto, el código activa el séptimo bit (pero no el sexto) en el grupo extraído de 5 bits con un OR 6464₁₀ = 1000000₂
( l & 31 | 64
).
Hasta ahora, el grupo de 5 bits tiene la forma: 10xxxxx₂
(el espacio sería 1011111₂ = 95₁₀
). Si podemos asignar espacio a 0
otros valores que no afectan, entonces podemos activar el sexto bit y eso debería ser todo. Esto es lo que mod 95
viene a jugar la parte, el espacio es 1011111₂ = 95₁₀
, usando la operación de modulación (l & 31 | 64) % 95)
solo vuelve el espacio 0
, y después de esto, el código activa el sexto bit al agregarlo 32₁₀ = 100000₂
al resultado anterior,((l & 31 | 64) % 95) + 32)
transformando el valor de 5 bits en un ascii válido personaje
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
El siguiente código realiza el proceso inverso, dada una cadena en minúscula (máximo 12 caracteres), devuelve el valor de 64 bits que podría usarse con el código del 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);
}
}
Agregar algo de valor a las respuestas anteriores. El siguiente script maravilloso imprime valores intermedios.
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));
}
Aquí está
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
¡Interesante!
Los caracteres ASCII estándar que están visibles están en el rango de 32 a 127.
Es por eso que ves 32 y 95 (127 - 32) allí.
De hecho, cada carácter se asigna a 5 bits aquí (puede encontrar la combinación de 5 bits para cada carácter), y luego todos los bits se concatenan para formar un gran número.
Los largos positivos son números de 63 bits, lo suficientemente grandes como para contener una forma cifrada de 12 caracteres. Por lo tanto, es lo suficientemente grande como para contenerlo Hello word
, pero para textos más grandes usará números más grandes, o incluso un BigInteger.
En una aplicación, queríamos transferir caracteres visibles en inglés, caracteres persas y símbolos por SMS. Como puede ver, hay 32 (number of Persian chars) + 95 (number of English characters and standard visible symbols) = 127
valores posibles, que se pueden representar con 7 bits.
Convertimos cada carácter UTF-8 (16 bits) a 7 bits, y ganamos más del 56% de relación de compresión. Entonces podríamos enviar mensajes de texto con el doble de longitud en la misma cantidad de SMS. (De alguna manera sucedió lo mismo aquí).
| 64
está haciendo.
Ha codificado caracteres como valores de 5 bits y ha empaquetado 11 de ellos en una longitud de 64 bits.
(packedValues >> 5*i) & 31
es el i-ésimo valor codificado con un rango de 0-31.
La parte difícil, como dices, es codificar el espacio. Las letras inglesas en minúsculas ocupan el rango contiguo 97-122 en Unicode (y ascii, y la mayoría de las otras codificaciones), pero el espacio es 32.
Para superar esto, usaste algo de aritmética. ((x+64)%95)+32
es casi lo mismo que x + 96
(tenga en cuenta cómo OR a nivel de bits es equivalente a la suma, en este caso), pero cuando x = 31, obtenemos 32
.
Imprime "hola mundo" por una razón similar que esto hace:
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));
pero por una razón algo diferente a esto:
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));
Sin una Oracle
etiqueta, era difícil ver esta pregunta. La generosidad activa me trajo aquí. Ojalá la pregunta tuviera otras etiquetas tecnológicas relevantes también :-(
Principalmente trabajo con ellos Oracle database
, así que usaría algunos Oracle
conocimientos para interpretar y explicar :-)
Vamos a convertir el número 4946144450195624
a binary
. Para eso utilizo un pequeño function
llamado dec2bin, es decir, decimal a binario .
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>
Usemos la función para obtener el valor binario:
SQL> SELECT dec2bin(4946144450195624) FROM dual;
DEC2BIN(4946144450195624)
--------------------------------------------------------------------------------
10001100100100111110111111110111101100011000010101000
SQL>
Ahora el truco es la 5-bit
conversión. Comience a agrupar de derecha a izquierda con 5 dígitos en cada grupo. Obtenemos :-
100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
Finalmente nos quedarían con solo 3 dígitos en el final a la derecha. Porque, tuvimos un total de 53 dígitos en la conversión binaria.
SQL> SELECT LENGTH(dec2bin(4946144450195624)) FROM dual;
LENGTH(DEC2BIN(4946144450195624))
---------------------------------
53
SQL>
hello world
el total tiene 11 caracteres (incluido el espacio), por lo que debemos agregar 2 bits al último grupo donde nos quedamos con solo 3 bits después de la agrupación.
Entonces, ahora tenemos: -
00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
Ahora, necesitamos convertirlo a un valor ascii de 7 bits. Para los personajes es fácil, solo necesitamos establecer el 6to y 7mo bit. Agregue 11
a cada grupo de 5 bits arriba a la izquierda.
Eso da :-
1100100|1101100|1110010|1101111|1110111|1111111|1101111|1101100|1101100|1100101|1101000
Interpretemos los valores binarios que usaré 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>
Veamos cada valor binario:
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>
Veamos qué personajes son: -
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>
Entonces, ¿qué obtenemos en la salida?
dlrow ⌂ olleh
Eso es hello⌂world a la inversa. El único problema es el espacio . Y la razón está bien explicada por @higuaro en su respuesta. Sinceramente, no pude interpretar el problema espacial por mí mismo en el primer intento, hasta que vi la explicación dada en su respuesta.
Encontré el código un poco más fácil de entender cuando se traduce a PHP, de la siguiente manera:
<?php
$result=0;
$bignum = 4946144450195624;
for (; $bignum > 0; $bignum >>= 5){
$result = (( $bignum & 31 | 64) % 95) + 32;
echo chr($result);
}
Ver código en vivo
out.println ((char) (((l & 31 | 64)% 95) + 32/1002439 * 1002439));
Para hacerlo mayúsculas: 3