web-dev-qa-db-fra.com

Quelle est l'idée derrière ^ = 32, qui convertit les lettres minuscules en majuscules et vice versa?

Je résolvais un problème de codeforces. Normalement, je vérifie d'abord si le caractère est une lettre anglaise supérieure ou inférieure, puis soustrais ou ajoute 32 pour le convertir dans la lettre correspondante. Mais j'ai trouvé quelqu'un qui ^= 32 pour faire la même chose. C'est ici:

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

J'ai cherché une explication à cela et je ne l'ai pas découvert. Alors pourquoi ça marche?

146
Devon

Jetons un coup d'œil à ASCII table de code en binaire.

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

Et 32 est 0100000 qui est la seule différence entre les lettres minuscules et majuscules. Donc, basculer ce bit fait basculer le cas d'une lettre.

149
Hanjoung Lee

Cela utilise le fait que ASCII ont été choisies par des gens vraiment intelligents.

foo ^= 32;

Ceci retourne le 6ème bit le plus bas1 de foo (l'indicateur majuscule de ASCII sorte de), transformant un ASCII majuscule en minuscule et vice-versa .

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

Exemple

'A' ^ 32

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

Et par propriété de XOR, 'a' ^ 32 == 'A'.

Remarquer

C++ n'est pas nécessaire d'utiliser ASCII pour représenter les caractères. Une autre variante est EBCDIC . Cette astuce ne fonctionne que sur ASCII plateformes. Une solution plus portable consisterait à utiliser std::tolower et std::toupper , avec le bonus offert pour être sensible aux paramètres régionaux (il ne résout cependant pas automatiquement tous vos problèmes, voir les commentaires):

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) Comme 32 est 1 << 5 (2 à la puissance 5), il retourne le 6ème bit (à partir de 1).

117
YSC

Permettez-moi de dire que c'est - même si cela semble intelligent - un hack vraiment, vraiment stupide. Si quelqu'un vous le recommande en 2019, frappez-le. Frappez-le aussi fort que possible.
Vous pouvez, bien sûr, le faire dans votre propre logiciel que vous et personne d'autre n'utilisez si vous savez que vous n'utiliserez de toute façon que l'anglais. Sinon, non.

Le hack était discutable "OK" il y a environ 30 à 35 ans lorsque les ordinateurs ne faisaient pas grand-chose mais l'anglais en ASCII, et peut-être un ou deux principaux Langues européennes. Mais ... ce n'est plus le cas.

Le hack fonctionne parce que les majuscules et les minuscules latino-américaines sont exactement 0x20 séparés les uns des autres et apparaissent dans le même ordre, ce qui ne représente qu'une différence. Ce qui, en fait, ce peu pirater, bascule.

Maintenant, les personnes créant des pages de codes pour l'Europe occidentale, et plus tard le consortium Unicode, étaient assez intelligentes pour conserver ce schéma, par exemple Trémas allemands et voyelles françaises. Ce n'est pas le cas pour ß qui (jusqu'à ce que quelqu'un ait convaincu le consortium Unicode en 2017 et qu'un grand magazine imprimé Fake News ait écrit à ce sujet, convaincant en fait le Duden - aucun commentaire à ce sujet) existe en versal (se transforme en SS). Maintenant, il existe existe en versal, mais les deux sont 0x1DBF positions à part, pas 0x20.

Les implémenteurs étaient cependant pas suffisamment prévenants pour que cela continue. Par exemple, si vous appliquez votre hack dans certaines langues d'Europe de l'Est ou similaires (je ne connais pas le cyrillique), vous obtiendrez une mauvaise surprise. Tous ces caractères "hache" en sont des exemples, les minuscules et les majuscules sont un à part. Le hack fait donc pas fonctionner correctement là-bas.

Il y a beaucoup plus à considérer, par exemple, certains caractères ne se transforment pas du tout simplement en minuscules en majuscules (ils sont remplacés par des séquences différentes), ou ils peuvent changer de forme (nécessitant différents points de code).

Ne pensez même pas à ce que ce hack fera pour des trucs comme le thaï ou le chinois (cela vous donnera juste un non-sens complet).

Il a peut-être été très utile d'enregistrer quelques centaines de cycles de processeur il y a 30 ans, mais de nos jours, il n'y a vraiment aucune excuse pour convertir une chaîne correctement. Il existe des fonctions de bibliothèque pour effectuer cette tâche non triviale.
Le temps nécessaire pour convertir correctement plusieurs dizaines de kilo-octets de texte est négligeable de nos jours.

35
Damon

Cela fonctionne car, en l'occurrence, la différence entre 'a' et A 'dans ASCII et les encodages dérivés est 32, et 32 ​​est également la valeur du sixième bit. Retourner le 6e bit avec un OR exclusif convertit ainsi entre le haut et le bas.

33
Jack Aidley

Votre implémentation du jeu de caractères sera très probablement ASCII. Si nous regardons le tableau:

enter image description here

Nous voyons qu'il y a une différence d'exactement 32 entre la valeur d'un nombre minuscule et majuscule. Par conséquent, si nous faisons ^= 32 (ce qui équivaut à basculer le 6e bit le moins significatif), il bascule entre un caractère minuscule et majuscule.

Notez que cela fonctionne avec tous les symboles, pas seulement les lettres. Il bascule un caractère avec le caractère respectif où le 6ème bit est différent, résultant en une paire de caractères qui est alternée entre les deux. Pour les lettres, les caractères majuscules/minuscules respectifs forment une telle paire. Un NUL se transformera en Space et inversement, et le @ bascule avec le backtick. Fondamentalement, n'importe quel caractère de la première colonne de ce graphique bascule avec le caractère sur une colonne, et il en va de même pour les troisième et quatrième colonnes.

Je n'utiliserais pas ce hack, car il n'y a aucune garantie que cela fonctionnera sur n'importe quel système. Utilisez simplement toupper et tolower à la place, et des requêtes telles que isupper .

22
Blaze

Beaucoup de bonnes réponses ici décrivent comment cela fonctionne, mais pourquoi cela fonctionne de cette façon est d'améliorer les performances. Les opérations au niveau du bit sont plus rapides que la plupart des autres opérations d'un processeur. Vous pouvez rapidement faire une comparaison insensible à la casse en ne regardant simplement pas le bit qui détermine la casse ou en changeant la casse en haut/bas simplement en inversant le bit (les gars qui ont conçu la table ASCII étaient assez intelligents) ).

De toute évidence, ce n'est pas aussi important aujourd'hui qu'il l'était en 1960 (lorsque les travaux ont commencé sur ASCII) en raison de processeurs plus rapides et d'Unicode, mais il existe encore des processeurs à faible coût qui pourraient faire une différence significative tant que vous ne pouvez garantir que ASCII caractères.

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

Sur les processeurs simples et peu coûteux, les opérations au niveau du bit sont généralement beaucoup plus rapides que la division, plusieurs fois plus rapides que la multiplication et parfois beaucoup plus rapides que l'addition.

REMARQUE: je recommanderais d'utiliser des bibliothèques standard pour travailler avec des chaînes pour un certain nombre de raisons (lisibilité, exactitude, portabilité, etc.). N'utilisez le retournement de bits que si vous avez mesuré les performances et qu'il s'agit de votre goulot d'étranglement.

15
Brian

C'est ainsi que fonctionne ASCII, c'est tout.

Mais en exploitant cela, vous abandonnez portabilité car C++ n'insiste pas sur ASCII comme encodage).

C'est pourquoi les fonctions std::toupper et std::tolower sont implémentés dans la bibliothèque standard C++ - vous devez les utiliser à la place.

14
Bathsheba

Voir le deuxième tableau à http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii , et les notes suivantes, reproduites ci-dessous:

Le modificateur Contrôle de votre clavier efface essentiellement les trois premiers bits de n'importe quel caractère que vous tapez, en laissant les cinq derniers et en le mappant sur la plage 0..31. Ainsi, par exemple, Ctrl-ESPACE, Ctrl- @ et Ctrl-`signifient tous la même chose: NUL.

Très vieux claviers utilisés pour faire Shift simplement en basculant le 32 ou 16 bits, selon la touche; c'est pourquoi la relation entre les minuscules et les majuscules en ASCII est si régulière, et la relation entre les chiffres et les symboles, et certaines paires de symboles, est en quelque sorte régulière si vous plissez les yeux. ASR-33, qui était un terminal tout en majuscules, vous a même permis de générer des caractères de ponctuation pour lesquels il n'avait pas de clés en décalant le 16 bits; ainsi, par exemple, Shift-K (0x4B) est devenu un [(0x5B)

ASCII a été conçu de telle sorte que le shift et ctrl les touches du clavier pourraient être implémentées sans beaucoup (ou peut-être aucune pour ctrl) logique - shift ne nécessitait probablement que quelques portes. Il était probablement au moins aussi logique de stocker le protocole filaire que tout autre codage de caractères (aucune conversion logicielle requise).

L'article lié explique également de nombreuses conventions de piratage étranges telles que And control H does a single character and is an old^H^H^H^H^H classic joke. ( trouvé ici ).

11
Iiridayn

Xoring avec 32 (00100000 en binaire) définit ou réinitialise le sixième bit (à partir de la droite). Cela équivaut strictement à ajouter ou à soustraire 32.

8
Yves Daoust

Les plages alphabétiques en minuscules et en majuscules ne traversent pas une frontière "d'alignement" %32 Dans le système de codage ASCII.

C'est pourquoi le bit 0x20 Est la seule différence entre les versions majuscules/minuscules de la même lettre.

Si ce n'était pas le cas, vous auriez besoin d'ajouter ou de soustraire 0x20, Pas seulement de basculer, et pour certaines lettres, il y aurait lieu de retourner d'autres bits supérieurs. (Et il n'y aurait pas une seule opération qui pourrait basculer, et la vérification des caractères alphabétiques en premier serait plus difficile parce que vous ne pourriez pas | = 0x20 pour forcer lcase.)


Astuces connexes ASCII uniquement: vous pouvez rechercher un caractère alphabétique ASCII en forçant les minuscules avec c |= 0x20 Puis en vérifiant si (non signé) c - 'a' <= ('z'-'a'). Donc juste 3 opérations: OR + SUB + CMP contre une constante 25. Bien sûr, les compilateurs savent comment optimiser (c>='a' && c<='z')en asm comme ceci pour vous, vous devriez donc tout au plus faire vous-même la partie c|=0x20. Il est plutôt gênant de faire vous-même tout le casting nécessaire, en particulier pour contourner les promotions d'entier par défaut vers int signé.

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

Voir aussi Convertir une chaîne en C++ en majuscules (chaîne SIMD toupper pour ASCII uniquement, masquant l'opérande pour XOR en utilisant cette vérification .)

Et aussi Comment accéder à un tableau de caractères et changer les lettres minuscules en majuscules, et vice versa (C avec SIMD intrinsèques et scalaire x86 asm casse-flip pour les caractères alphabétiques ASCII , laissant les autres non modifiés.)


Ces astuces ne sont généralement utiles que si vous optimisez manuellement certains traitements de texte avec SIMD (par exemple SSE2 ou NEON), après avoir vérifié qu'aucun des char d'un vecteur n'a son bit élevé défini. (Et donc aucun des octets ne fait partie d'un codage UTF-8 multi-octets pour un seul caractère, qui peut avoir des inverses majuscules/minuscules différents). Si vous en trouvez, vous pouvez revenir au scalaire pour ce bloc de 16 octets, ou pour le reste de la chaîne.

Il existe même des paramètres régionaux où toupper() ou tolower() sur certains caractères de la plage ASCII produisent des caractères en dehors de cette plage, notamment le turc où je ↔ ı et İ ↔ i. Dans ces paramètres régionaux, vous auriez besoin d'une vérification plus sophistiquée, ou probablement ne pas essayer d'utiliser cette optimisation du tout.


Mais dans certains cas, vous êtes autorisé à supposer ASCII au lieu de UTF-8, par exemple Utilitaires Unix avec LANG=C (L'environnement local POSIX), pas en_CA.UTF-8 Ou autre.

Mais si vous pouvez vérifier que c'est sûr, vous pouvez toupper des chaînes de longueur moyenne beaucoup plus rapide que d'appeler toupper() dans une boucle (comme 5x), et j'ai testé avec Boost 1.58 pour la dernière fois , beaucoup beaucoup plus rapide que boost::to_upper_copy<char*, std::string>() qui fait un stupide dynamic_cast pour chaque personnage.

7
Peter Cordes