Qual è l'idea dietro ^ = 32, che converte le lettere minuscole in maiuscolo e viceversa?


146

Stavo risolvendo alcuni problemi sulle forze di codice. Normalmente controllo prima se il carattere è una lettera inglese superiore o inferiore, quindi sottraggo o aggiungo 32per convertirlo nella lettera corrispondente. Ma ho trovato qualcuno che ^= 32fa la stessa cosa. Ecco qui:

char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a

Ho cercato una spiegazione per questo e non l'ho scoperto. Allora perché funziona?


5
en.wikipedia.org/wiki/File:USASCII_code_chart.png Suggerimento: puoi convertire @in `usando ^ 32.
KamilCuk,

112
FWIW, in realtà non "funziona". Funziona con questo particolare set di caratteri, ma ci sono altri set in cui non è necessario utilizzare touppere tolowerper cambiare caso.
NathanOliver,

7
a volte con i concorsi online "l'idea" è scrivere codice in modo così offuscato da non passare mai una recensione seria;)
idclev 463035818

21
^ = sta trasformando il valore usando XOR. Le lettere ASCII maiuscole hanno uno zero nel bit corrispondente, mentre le lettere minuscole ne hanno uno. Detto questo, per favore non farlo! Utilizzare routine di caratteri (unicode) appropriate per convertire tra lettere minuscole e maiuscole. L'era della sola ASCII è ormai lontana.
Hans-Martin Mosner,

14
Non è solo che funziona solo con alcuni set di caratteri. Anche se supponiamo che tutto il mondo sia UTF-8 (che potrebbe almeno essere un bel obiettivo utopico), funziona anche solo con le 26 lettere Aa Z. Va bene purché ti interessi solo dell'inglese (e non usare ortografia "ingenua", parole come "caffè" o nomi con segni diacritici ...), ma il mondo non è solo inglese.
ilkkachu,

Risposte:


149

Diamo un'occhiata alla tabella dei codici ASCII in binario.

A 1000001    a 1100001
B 1000010    b 1100010
C 1000011    c 1100011
...
Z 1011010    z 1111010

E 32 è 0100000l'unica differenza tra lettere minuscole e maiuscole. Quindi alternare quel bit commuta il caso di una lettera.


49
"attiva / disattiva il caso" * solo per ASCII
Mooing Duck il

39
@Mooing solo per A-Za-z in ASCII. La minuscola di "[" non è "{".
dbkk,

21
@dbkk {è più corto di [, quindi è un caso "inferiore". No? Ok, mi faccio vedere: D
Peter Badida,

25
Curiosità: nell'area dei 7 bit, i computer tedeschi avevano [] {|} rimappato su ÄÖÜäöü poiché avevamo bisogno di Umlaut più di quei personaggi, quindi in quel contesto {(ä) era in realtà il minuscolo [(Ä).
Guntram Blohm supporta Monica il

14
@GuntramBlohm Ulteriore curiosità, ecco perché i server IRC considerano foobar[] e foobar{}sono nickname identici, poiché i nickname non fanno distinzione tra maiuscole e minuscole e IRC ha le sue origini in Scandinavia :)
ZeroKnight

117

Questo utilizza il fatto che i valori ASCII sono stati scelti da persone davvero intelligenti.

foo ^= 32;

Questo inverte il sesto bit più basso 1 di foo(il flag maiuscolo di tipo ASCII), trasformando un maiuscolo ASCII in minuscolo e viceversa .

+---+------------+------------+
|   | Upper case | Lower case |  32 is 00100000
+---+------------+------------+
| A | 01000001   | 01100001   |
| B | 01000010   | 01100010   |
|            ...              |
| Z | 01011010   | 01111010   |
+---+------------+------------+

Esempio

'A' ^ 32

    01000001 'A'
XOR 00100000 32
------------
    01100001 'a'

E per la proprietà di XOR, 'a' ^ 32 == 'A'.

Avviso

C ++ non è necessario per utilizzare ASCII per rappresentare i caratteri. Un'altra variante è EBCDIC . Questo trucco funziona solo su piattaforme ASCII. Una soluzione più portatile sarebbe quella di utilizzare std::tolowere std::toupper, con il bonus offerto di essere a conoscenza delle impostazioni locali (non risolve automagicamente tutti i tuoi problemi, vedi commenti):

bool case_incensitive_equal(char lhs, char rhs)
{
    return std::tolower(lhs, std::locale{}) == std::tolower(rhs, std::locale{}); // std::locale{} optional, enable locale-awarness
}

assert(case_incensitive_equal('A', 'a'));

1) Come 32 è 1 << 5(2 alla potenza 5), ​​lancia il 6 ° bit (contando da 1).


16
EBCDIC è stato scelto anche da alcune persone molto intelligenti: funziona davvero bene su schede perforate cfr. ASCII che è un casino. Ma questa è una bella risposta, +1.
Bathsheba,

65
Non conosco le schede perforate, ma ASCII è stato utilizzato su nastro di carta. Ecco perché il carattere Elimina è codificato come 1111111: Quindi puoi contrassegnare qualsiasi carattere come "cancellato" punzonando tutti i fori nella sua colonna sul nastro.
dan04,

23
@Bathsheba come qualcuno che non ha usato una scheda perforata, è molto difficile avvolgere la mia testa intorno all'idea che EBCDIC sia stato progettato in modo intelligente.
Lord Farquaad,

9
@LordFarquaad IMHO l'immagine di Wikipedia di come le lettere sono scritte su una scheda perforata è un'ovvia illustrazione di come EBCDIC abbia un senso (ma non totale, vedi / vs S) per questa codifica. en.wikipedia.org/wiki/EBCDIC#/media/…
Peteris,

11
@ dan04 Nota per menzionare "qual è la forma minuscola di 'MASSE'?". Per quelli che non lo sanno, ci sono due parole in tedesco la cui forma maiuscola è MASSE; uno è "Masse" e l'altro è "Maße". Corretto tolowerin tedesco non ha solo bisogno di un dizionario, deve essere in grado di analizzarne il significato.
Martin Bonner supporta Monica il

35

Permettetemi di dire che questo è - sebbene sembri intelligente - un hack davvero, davvero stupido. Se qualcuno ti consiglia questo nel 2019, colpiscilo. Colpiscilo più forte che puoi.
Ovviamente puoi farlo nel tuo software che tu e nessun altro usate se sapete che non userete mai nessuna lingua tranne l'inglese. Altrimenti, non andare.

L'hacking era discutibile "OK" circa 30-35 anni fa, quando i computer non facevano molto altro che l'inglese in ASCII e forse una o due principali lingue europee. Ma ... non è più così.

L'hacking funziona perché le maiuscole e minuscole latino-americane sono esattamente 0x20separate l'una dall'altra e appaiono nello stesso ordine, il che è solo un po 'di differenza. Che, in realtà, questo bit hack, attiva / disattiva.

Ora, le persone che creavano pagine di codice per l'Europa occidentale, e in seguito il consorzio Unicode, erano abbastanza intelligenti da mantenere questo schema per esempio Umlaut tedesche e vocali con accento francese. Non così per ß che (fino a quando qualcuno non ha convinto il consorzio Unicode nel 2017, e una grande rivista di stampa Fake News ne ha scritto, in realtà convincendo il Duden - nessun commento al riguardo) non esiste nemmeno come un versal (si trasforma in SS) . Ora non esiste come versale, ma i due sono 0x1DBFposizioni a parte, non è 0x20.

Gli implementatori, tuttavia, non sono stati abbastanza premurosi per continuare. Ad esempio, se applichi il tuo hack in alcune lingue dell'Europa orientale o simili (non saprei sul cirillico), otterrai una brutta sorpresa. Tutti quei personaggi "ascia" ne sono un esempio, lettere minuscole e maiuscole sono un tutt'uno. L'hacking quindi non funziona correttamente lì.

C'è molto di più da considerare, ad esempio, alcuni caratteri non si trasformano affatto da maiuscoli a minuscoli (vengono sostituiti con sequenze diverse), oppure possono cambiare forma (che richiedono punti di codice diversi).

Non pensare nemmeno a ciò che questo hack farà a cose come Thai o Cinese (ti darà solo una totale assurdità).

Salvare un paio di centinaia di cicli della CPU potrebbe essere stato molto utile 30 anni fa, ma al giorno d'oggi, non ci sono davvero scuse per convertire correttamente una stringa. Ci sono funzioni di libreria per eseguire questo compito non banale.
Al giorno d'oggi il tempo impiegato per convertire correttamente diverse decine di kilobyte di testo è trascurabile.


2
Sono assolutamente d'accordo - anche se è una buona idea per ogni programmatore sapere perché funziona - potrebbe anche fare una buona domanda per l'intervista. Cosa fa questo e quando dovrebbe essere usato :)
Bill K

33

Funziona perché, come accade, la differenza tra 'a' e A 'in ASCII e le codifiche derivate è 32 e 32 è anche il valore del sesto bit. Lanciando il sesto bit con un OR esclusivo si converte quindi tra superiore e inferiore.


22

Molto probabilmente l'implementazione del set di caratteri sarà ASCII. Se guardiamo al tavolo:

inserisci qui la descrizione dell'immagine

Vediamo che c'è una differenza esattamente 32tra il valore di un numero minuscolo e maiuscolo. Pertanto, se lo facciamo ^= 32(il che equivale a alternare il sesto bit meno significativo), cambia tra un carattere minuscolo e maiuscolo.

Nota che funziona con tutti i simboli, non solo con le lettere. Attiva o disattiva un personaggio con il rispettivo carattere in cui il sesto bit è diverso, risultando in una coppia di caratteri che viene alternata avanti e indietro. Per le lettere, i rispettivi caratteri maiuscoli / minuscoli formano una tale coppia. A NULcambierà Spacee viceversa, e si @alternerà con il backtick. Fondamentalmente qualsiasi carattere nella prima colonna su questo grafico si alterna con il carattere una colonna sopra, e lo stesso vale per la terza e la quarta colonna.

Non userei questo hack però, poiché non c'è garanzia che funzioni su qualsiasi sistema. Basta usare toupper e tolower invece e query come isupper .


2
Bene, non funziona per tutte le lettere che hanno una differenza di 32. Altrimenti, funzionerebbe tra '@' e ''!
Matthieu Brucher,

2
@MatthieuBrucher Funziona, 32 ^ 32è 0, non 64
NathanOliver

5
'@' e '' non sono "lettere". Solo [a-z]e [A-Z]sono "lettere". Il resto sono coincidenze che seguono la stessa regola. Se qualcuno ti chiedesse di "maiuscole]", quale sarebbe? sarebbe comunque "]" - "}" non è il "maiuscolo" di "]".
freedomn-m,

4
@MatthieuBrucher: Un altro modo per chiarire questo punto è che gli intervalli alfabetici minuscoli e maiuscoli non attraversano un %32limite di "allineamento" nel sistema di codifica ASCII. Questo è il motivo per cui bit 0x20è l'unica differenza tra le versioni maiuscole / minuscole della stessa lettera. Se così non fosse, avresti bisogno di aggiungere o sottrarre 0x20, non solo attivare e disattivare, e per alcune lettere verrebbe eseguito per capovolgere altri bit più alti. (E la stessa operazione non poteva essere attivata e, in primo luogo, verificare la presenza di caratteri alfabetici sarebbe più difficile perché non si poteva |= 0x20forzare il laccio.)
Peter Cordes,

2
+1 per avermi ricordato di tutte quelle visite su asciitable.com per fissare quella grafica esatta (e la versione estesa ASCII !!) degli ultimi, non so, 15 o 20 anni?
AC

15

Molte buone risposte qui che descrivono come funziona, ma perché funziona in questo modo è migliorare le prestazioni. Le operazioni bit a bit sono più veloci della maggior parte delle altre operazioni all'interno di un processore. Puoi fare rapidamente un confronto senza distinzione tra maiuscole e minuscole semplicemente non guardando il bit che determina il caso o cambia maiuscole / minuscole in alto / basso semplicemente girando il bit (quei ragazzi che hanno progettato la tabella ASCII erano piuttosto intelligenti).

Ovviamente, questo non è un grosso problema oggi come lo era nel 1960 (quando il lavoro iniziò su ASCII) a causa di processori più veloci e Unicode, ma ci sono ancora alcuni processori a basso costo che questo potrebbe fare una differenza significativa purché sia ​​possibile garantire solo caratteri ASCII.

https://en.wikipedia.org/wiki/Bitwise_operation

Su semplici processori a basso costo, in genere, le operazioni bit a bit sono sostanzialmente più veloci della divisione, molte volte più veloci della moltiplicazione e talvolta significativamente più veloci dell'aggiunta.

NOTA: consiglierei di utilizzare le librerie standard per lavorare con le stringhe per una serie di motivi (leggibilità, correttezza, portabilità, ecc.). Usa il bit flipping solo se hai misurato le prestazioni e questo è il tuo collo di bottiglia.


14

È come funziona ASCII, tutto qui.

Ma sfruttando questo, stai rinunciando alla portabilità poiché C ++ non insiste su ASCII come codifica.

Questo è il motivo per cui le funzioni std::touppere std::tolowersono implementate nella libreria standard C ++ - dovresti invece usarle.


6
Tuttavia, esistono protocolli che richiedono l'utilizzo di ASCII, ad esempio DNS. In effetti, il "trucco 0x20" viene utilizzato da alcuni server DNS per inserire entropia aggiuntiva in una query DNS come meccanismo anti-spoofing. Il DNS non fa distinzione tra maiuscole e minuscole, ma dovrebbe anche preservare il caso, quindi se si invia una query con caso casuale e si ottiene lo stesso caso, è una buona indicazione che la risposta non è stata falsificata da terzi.
Alnitak,

Vale la pena ricordare che molte codifiche hanno ancora la stessa rappresentazione per i caratteri ASCII standard (non estesi). Tuttavia, se sei davvero preoccupato per le diverse codifiche, dovresti usare le funzioni appropriate.
Captain Man,

5
@CaptainMan: Assolutamente. UTF-8 è una cosa di pura bellezza. Si spera che venga "assorbito" nello standard C ++ nella misura in cui IEEE754 ha per virgola mobile.
Bathsheba,

11

Vedere la seconda tabella all'indirizzo http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii e le note seguenti, riportate di seguito:

Il modificatore di controllo sulla tastiera cancella sostanzialmente i primi tre bit di qualunque carattere digiti, lasciando i cinque inferiori e mappandolo nell'intervallo 0..31. Quindi, ad esempio, Ctrl-SPACE, Ctrl- @ e Ctrl-`significano tutti la stessa cosa: NUL.

Tastiere molto vecchie usavano fare Shift semplicemente attivando i 32 o 16 bit, a seconda del tasto; questo è il motivo per cui la relazione tra lettere minuscole e maiuscole in ASCII è così regolare, e la relazione tra numeri e simboli, e alcune coppie di simboli, è in qualche modo normale se lo osservi. L'ASR-33, che era un terminale tutto maiuscolo, ti permetteva persino di generare alcuni caratteri di punteggiatura per i quali non aveva le chiavi spostando il 16 bit; quindi, ad esempio, Shift-K (0x4B) è diventato un [(0x5B)

ASCII è stato progettato in modo tale che i tasti shifte della ctrltastiera possano essere implementati senza molta (o forse nessuna ctrllogica) - shiftprobabilmente richiedono solo poche porte. Probabilmente aveva almeno lo stesso senso memorizzare il protocollo del filo come qualsiasi altra codifica dei caratteri (non è richiesta la conversione del software).

L'articolo collegato spiega anche molte strane convenzioni di hacker come And control H does a single character and is an old^H^H^H^H^H classic joke.( trovato qui ).


1
Potrebbe implementare un interruttore a scorrimento per più di ASCII con / foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20, sebbene questo sia solo ASCII e quindi poco saggio per i motivi indicati in altre risposte. Probabilmente può anche essere migliorato con una programmazione senza rami.
Iiridayn

1
Ah, foo ^= 0x20 >> !(foo & 0x40)sarebbe più semplice. Anche un buon esempio del perché il codice terse è spesso considerato illeggibile ^ _ ^.
Iiridayn

8

Xoring con 32 (00100000 in binario) imposta o reimposta il sesto bit (da destra). Ciò è strettamente equivalente all'aggiunta o alla sottrazione 32.


2
Un altro modo per dirlo è che XOR è add-without-carry.
Peter Cordes,

7

Gli intervalli alfabetici minuscoli e maiuscoli non attraversano un %32limite di "allineamento" nel sistema di codifica ASCII.

Questo è il motivo per cui bit 0x20è l'unica differenza tra le versioni maiuscole / minuscole della stessa lettera.

Se così non fosse, avresti bisogno di aggiungere o sottrarre 0x20, non solo attivare e disattivare, e per alcune lettere verrebbe eseguito per capovolgere altri bit più alti. (E non ci sarebbe una singola operazione che potrebbe alternare, e in primo luogo verificare la presenza di caratteri alfabetici sarebbe più difficile perché non potresti | = 0x20 per forzare lcase.)


Trucchi solo ASCII correlati: è possibile verificare la presenza di un carattere ASCII alfabetico forzando le lettere minuscole con c |= 0x20e quindi se (non firmato) c - 'a' <= ('z'-'a'). Quindi solo 3 operazioni: OR + SUB + CMP contro un costante 25. Naturalmente, i compilatori sanno come ottimizzare (c>='a' && c<='z') in asm come questo per te , quindi al massimo dovresti fare la c|=0x20parte da solo. È piuttosto scomodo fare tutto il cast necessario, in particolare per aggirare le promozioni di numeri interi predefiniti da firmare int.

unsigned char lcase = y|0x20;
if (lcase - 'a' <= (unsigned)('z'-'a')) {   // lcase-'a' will wrap for characters below 'a'
    // c is alphabetic ASCII
}
// else it's not

Vedi anche Converti una stringa in C ++ in maiuscolo (stringa SIMD touppersolo per ASCII, mascherando l'operando per XOR usando quel controllo).

E anche Come accedere a un array di caratteri e cambiare le lettere minuscole in lettere maiuscole e viceversa (C con intrinseche SIMD e scalare x86 asm maiuscole / minuscole per caratteri ASCII alfabetici, lasciando gli altri non modificati).


Questi trucchi sono per lo più utili solo se si ottimizza a mano alcune elaborazioni di testo con SIMD (es. SSE2 o NEON), dopo aver verificato che nessuna delle chars in un vettore abbia il bit alto impostato. (E quindi nessuno dei byte fa parte di una codifica UTF-8 multi-byte per un singolo carattere, che potrebbe avere diverse maiuscole / minuscole). Se ne trovi, puoi tornare allo scalare per questo blocco di 16 byte o per il resto della stringa.

Ci sono anche alcuni locali in cui toupper()o tolower()su alcuni caratteri nell'intervallo ASCII producono caratteri al di fuori di tale intervallo, in particolare il turco dove I ↔ ı e İ ↔ i. In quei locali, avresti bisogno di un controllo più sofisticato, o probabilmente non tenterai affatto di utilizzare questa ottimizzazione.


Ma in alcuni casi, ti è consentito assumere ASCII invece di UTF-8, ad esempio utilità Unix con LANG=C(la locale POSIX), non en_CA.UTF-8o altro.

Ma se riesci a verificare che è sicuro, puoi toupperstringhe di media lunghezza molto più velocemente rispetto a chiamare toupper()in un ciclo (come 5x), e alla fine ho provato con Boost 1.58 , molto più veloce di quello boost::to_upper_copy<char*, std::string>()che fa uno stupido dynamic_castper ogni personaggio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.