Ho scoperto questa stranezza:
for (long l = 4946144450195624l; l > 0; l >>= 5)
System.out.print((char) (((l & 31 | 64) % 95) + 32));
Produzione:
hello world
Come funziona?
Ho scoperto questa stranezza:
for (long l = 4946144450195624l; l > 0; l >>= 5)
System.out.print((char) (((l & 31 | 64) % 95) + 32));
Produzione:
hello world
Come funziona?
Risposte:
Il numero si 4946144450195624
adatta a 64 bit, la sua rappresentazione binaria è:
10001100100100111110111111110111101100011000010101000
Il programma decodifica un carattere per ogni gruppo a 5 bit, da destra a sinistra
00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
d | l | r | o | w | | o | l | l | e | h
Per 5 bit, è possibile rappresentare 2⁵ = 32 caratteri. L'alfabeto inglese contiene 26 lettere, questo lascia spazio a 32 - 26 = 6 simboli a parte le lettere. Con questo schema di codificazione puoi avere tutte e 26 le lettere inglesi (un caso) e 6 simboli (tra cui lo spazio).
Il >>= 5
nel ciclo for salta da un gruppo all'altro, quindi il gruppo a 5 bit viene isolato ANDing il numero con la maschera 31₁₀ = 11111₂
nella frasel & 31
Ora il codice mappa il valore a 5 bit sul corrispondente carattere ASCII a 7 bit. Questa è la parte difficile, controlla le rappresentazioni binarie per le lettere dell'alfabeto minuscole nella seguente tabella:
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
Qui puoi vedere che i caratteri ASCII che vogliamo mappare iniziano con il settimo e il sesto bit set ( 11xxxxx₂
) (ad eccezione dello spazio, che ha solo il sesto bit attivato), puoi OR
codificare a 5 bit con 96
(96₁₀ = 1100000₂
) e dovrebbe essere abbastanza per fare la mappatura, ma che non funzionerebbe per lo spazio (maledetto spazio!)
Ora sappiamo che è necessario prestare particolare attenzione a elaborare lo spazio contemporaneamente agli altri personaggi. Per ottenere ciò, il codice attiva il 7o bit (ma non il 6o) sul gruppo a 5 bit estratto con un OR 64 64₁₀ = 1000000₂
( l & 31 | 64
).
Finora il gruppo a 5 bit ha la forma: 10xxxxx₂
(lo spazio sarebbe 1011111₂ = 95₁₀
). Se siamo in grado di mappare lo spazio senza 0
influenzare altri valori, allora possiamo attivare il sesto bit e dovrebbe essere tutto. Ecco a cosa serve la mod 95
parte, lo spazio è 1011111₂ = 95₁₀
, usando l'operazione mod (l & 31 | 64) % 95)
torna solo lo spazio 0
, e dopo questo, il codice attiva il sesto bit aggiungendo 32₁₀ = 100000₂
al risultato precedente, ((l & 31 | 64) % 95) + 32)
trasformando il valore a 5 bit in un ASCII valido carattere
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
Il seguente codice esegue il processo inverso, data una stringa minuscola (max 12 caratteri), restituisce il valore lungo 64 bit che potrebbe essere utilizzato con il codice 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);
}
}
Aggiungendo un valore alle risposte sopra. Lo script groovy che segue stampa valori intermedi.
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));
}
Ecco qui
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
Interessante!
I caratteri ASCII standard che sono visibili sono compresi tra 32 e 127.
Ecco perché vedi 32 e 95 (127 - 32) lì.
In effetti, ogni carattere è mappato su 5 bit qui (è possibile trovare una combinazione di 5 bit per ciascun carattere), quindi tutti i bit vengono concatenati per formare un numero elevato.
I long positivi sono numeri a 63 bit, abbastanza grandi da contenere una forma criptata di 12 caratteri. Quindi è abbastanza grande da contenere Hello word
, ma per testi più grandi dovrai usare numeri più grandi o anche un BigInteger.
In un'applicazione volevamo trasferire caratteri inglesi, caratteri persiani e simboli visibili tramite SMS. Come vedi ci sono 32 (number of Persian chars) + 95 (number of English characters and standard visible symbols) = 127
possibili valori, che possono essere rappresentati con 7 bit.
Abbiamo convertito ogni carattere UTF-8 (16 bit) in 7 bit e abbiamo ottenuto un rapporto di compressione superiore al 56%. Quindi potremmo inviare messaggi di lunghezza doppia nello stesso numero di SMS. (In qualche modo è successa la stessa cosa qui).
| 64
sta facendo.
Hai codificato i caratteri come valori a 5 bit e ne hai confezionati 11 in 64 bit.
(packedValues >> 5*i) & 31
è l'i-esimo valore codificato con un intervallo 0-31.
La parte difficile, come dici tu, è codificare lo spazio. Le lettere inglesi minuscole occupano l'intervallo contiguo 97-122 in Unicode (e ascii e la maggior parte delle altre codifiche), ma lo spazio è 32.
Per ovviare a questo, hai usato un po 'di aritmetica. ((x+64)%95)+32
è quasi uguale a x + 96
(nota come OR bit per bit è equivalente all'aggiunta, in questo caso), ma quando x = 31, otteniamo 32
.
Stampa "ciao mondo" per un motivo simile che fa:
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));
ma per una ragione leggermente diversa da questa:
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));
Senza un Oracle
tag, era difficile vedere questa domanda. La generosità attiva mi ha portato qui. Vorrei che la domanda avesse anche altri tag tecnologici rilevanti :-(
Lavoro principalmente con Oracle database
, quindi vorrei usare alcune Oracle
conoscenze per interpretare e spiegare :-)
Convertiamo il numero 4946144450195624
in binary
. Per questo uso un piccolo function
chiamato dec2bin, cioè da decimale 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>
Usiamo la funzione per ottenere il valore binario -
SQL> SELECT dec2bin(4946144450195624) FROM dual;
DEC2BIN(4946144450195624)
--------------------------------------------------------------------------------
10001100100100111110111111110111101100011000010101000
SQL>
Ora il trucco è la 5-bit
conversione. Inizia a raggruppare da destra a sinistra con 5 cifre in ciascun gruppo. Noi abbiamo :-
100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
Alla fine saremmo rimasti con solo 3 cifre alla fine a destra. Perché, abbiamo avuto un totale di 53 cifre nella conversione binaria.
SQL> SELECT LENGTH(dec2bin(4946144450195624)) FROM dual;
LENGTH(DEC2BIN(4946144450195624))
---------------------------------
53
SQL>
hello world
il totale ha 11 caratteri (incluso lo spazio), quindi dobbiamo aggiungere 2 bit all'ultimo gruppo dove eravamo rimasti con solo 3 bit dopo il raggruppamento.
Quindi, ora abbiamo: -
00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
Ora, dobbiamo convertirlo in valore ASCII a 7 bit. Per i personaggi è facile, dobbiamo solo impostare il 6 ° e il 7 ° bit. Aggiungi 11
a ciascun gruppo a 5 bit in alto a sinistra.
Questo dà: -
1100100|1101100|1110010|1101111|1110111|1111111|1101111|1101100|1101100|1100101|1101000
Interpretiamo i valori binari, userò 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>
Diamo un'occhiata a ciascun valore 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>
Diamo un'occhiata a quali personaggi sono: -
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>
Quindi, cosa otteniamo nell'output?
dlrow ⌂ olleh
Questo è ciao-mondo al contrario. L'unico problema è lo spazio . E il motivo è ben spiegato da @higuaro nella sua risposta. Onestamente non ho potuto interpretare il problema dello spazio da solo al primo tentativo, fino a quando non ho visto la spiegazione fornita nella sua risposta.
Ho trovato il codice leggermente più semplice da capire quando tradotto in PHP, come segue:
<?php
$result=0;
$bignum = 4946144450195624;
for (; $bignum > 0; $bignum >>= 5){
$result = (( $bignum & 31 | 64) % 95) + 32;
echo chr($result);
}
Vedi codice live
out.println ((char) (((l & 31 | 64)% 95) + 32/1002439 * 1002439));
Per farlo tappare: 3