1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • En ASCII, Z est placé à 90 et a à 97, et les 6 caractères entre les deux font que l’écart de code entre majuscules et minuscules est exactement de 32
  • Comme 32 vaut 2^5, deux lettres correspondantes comme A 65 et a 97 ne diffèrent toujours que d’un seul bit : 00100000
  • Grâce à cette disposition, faire un AND avec le complément binaire de 32 met en majuscule, faire un OR avec 32 met en minuscule, et faire un XOR avec 32 inverse la casse
  • On peut obtenir le rang alphabétique en faisant un AND avec 31 sur le code du caractère pour ne garder que les 5 bits de poids faible ; A/a vaut alors 1, et Z/z vaut 26
  • ASCII est un ancien encodage de caractères en 7 bits qui ne représente que 128 points de code ; aujourd’hui, les 128 premiers points de code de Unicode sont identiques à ASCII

La disposition ASCII et l’écart de 32

  • Dans la table ASCII, le code de la majuscule Z vaut 90, et la minuscule a n’est pas placée juste après, mais à 97
  • Entre les deux se trouvent les 6 caractères [ \ ] ^ _ `
  • L’alphabet anglais compte 26 lettres, et en ajoutant ces 6 caractères on obtient 26 + 6 = 32
  • 32 correspond à 2^5, ce qui aligne la correspondance entre majuscules et minuscules sur la différence d’un bit précis
  • Par exemple, A vaut 65 soit 01000001, et a vaut 97 soit 01100001 ; la différence entre les deux est de 32

La relation entre ASCII et Unicode

  • ASCII est l’un des premiers systèmes d’encodage de caractères ; il n’utilise que 7 bits pour représenter 2^7 = 128 points de code
  • Ces 128 points de code ne suffisent pas à contenir tous les caractères utilisés par les humains, en particulier pour des langues comme le chinois qui comptent des dizaines de milliers de caractères
  • Aujourd’hui, l’ensemble de caractères standard est Unicode, avec plusieurs encodages comme UTF-8 et UTF-16
  • Les 128 premiers points de code d’Unicode sont identiques à ASCII

Le 5e bit qui sépare majuscules et minuscules

  • Si l’on compare en binaire une majuscule et sa minuscule correspondante, c’est toujours le bit correspondant à 32 qui change
65  = 01000001 = A
97  = 01100001 = a

66  = 01000010 = B
98  = 01100010 = b

67  = 01000011 = C
99  = 01100011 = c
  • 32 s’écrit 00100000 en binaire, et ce seul bit crée la différence entre majuscule et minuscule
  • La disposition des lettres dans ASCII permet ainsi de transformer facilement la casse avec des opérations sur les bits

Traiter la casse avec des opérations binaires

  • Conversion en majuscules

    • Pour transformer un caractère en majuscule, on effectue un AND binaire avec le complément de 32
0 1 1 0 0 0 0 1 (97 = 'a')
& 1 1 0 1 1 1 1 1 (mask)
-------------------
0 1 0 0 0 0 0 1 (65 = 'A')
  • Appliqué à a, 97 devient 65, donc A
  • Si on applique la même opération à A, il reste A
  • Conversion en minuscules

    • Pour transformer un caractère en minuscule, on effectue un OR binaire avec 32
0 1 0 0 0 0 0 1 (65 = 'A')
| 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 1 0 0 0 0 1 (97 = 'a')
  • Appliqué à A, 65 devient 97, donc a
  • Si on applique la même opération à a, il reste a
  • Inversion de casse

    • Pour inverser la casse, on effectue un XOR binaire avec 32
0 1 1 0 0 0 0 1 (97 = 'a')
^ 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 0 0 0 0 0 1 (65 = 'A')

0 1 0 0 0 0 0 1 (65 = 'A')
^ 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 1 0 0 0 0 1 (97 = 'a')
  • a devient A, et A devient a

Obtenir le rang alphabétique avec les 5 bits de poids faible

  • On peut obtenir le rang alphabétique en faisant un AND binaire avec 31 sur le code du caractère
0 1 0 0 0 0 0 1 (65 = 'A')
& 0 0 0 1 1 1 1 1 (31)
-------------------
0 0 0 0 0 0 0 1 (1)

0 1 1 1 1 0 1 0 (122 = 'z')
& 0 0 0 1 1 1 1 1 (31)
-------------------
0 0 0 1 1 0 1 0 (26)
  • 31 s’écrit 00011111 en binaire ; il efface les bits de poids fort et ne conserve que les 5 bits de poids faible
  • En ASCII, les 5 bits de poids faible des lettres correspondent exactement à leur position dans l’alphabet
  • A/a se termine par 00001 et vaut donc 1, B/b se termine par 00010 et vaut donc 2, et Z/z se termine par 11010 et vaut donc 26
  • Dans les codes de caractères ASCII, c & 31 est équivalent à c % 32
  • Comme 32 est une puissance de 2, faire un masquage avec 31 supprime les blocs de taille 32 et ne conserve que le reste
'A' = 65  → 65 % 32 = 1
'B' = 66  → 66 % 32 = 2
...
'Z' = 90  → 90 % 32 = 26
'a' = 97  → 97 % 32 = 1
'b' = 98  → 98 % 32 = 2
...
'z' = 122 → 122 % 32 = 26

1 commentaires

 
GN⁺ 4 시간 전
Commentaires sur Lobste.rs
  • L’explication est correcte, mais j’ai l’impression que https://garbagecollected.org/2017/01/31/four-column-ascii/ l’explique mieux
    Ce n’est pas seulement une question de Shift, Ctrl est aussi concerné. Par exemple, Tab est Ctrl-I, et comme I vaut 1001001, Ctrl masque le premier bit pour laisser 0001001, soit Tab

  • Si l’on utilise une logique électromécanique plutôt qu’une logique électronique, comme les constructeurs des années 1960, un bit paired keyboard est bien plus simple à implémenter
    La touche Shift n’a qu’à basculer un seul bit dans le caractère ASCII. Aujourd’hui, on met un CPU générique dans chaque clavier et la logique ne coûte pratiquement rien, mais à l’époque où les ordinateurs génériques occupaient une pièce entière, ce genre de solution coûtait bien plus cher

  • L’article présente comme motivation de la disposition ASCII le fait de pouvoir implémenter efficacement la conversion majuscules/minuscules par opérations sur les bits, mais cette valeur semble aujourd’hui assez mince, même si elle a pu compter historiquement
    La conversion a→A ne fonctionne que pour du texte simple, et même si ISO-8859-1 ou Unicode étendent partiellement cette disposition à des caractères comme ü ou ç, la casse dépend du lieu, du contexte et de l’époque. La correspondance correcte en majuscules/minuscules pour ß ou ï dépend de la langue, des autorités de normalisation, de la région d’usage et du moment historique
    Unicode fournit des tables de correspondance et de pliage de casse comme https://www.unicode.org/charts/case/ qui donnent une réponse assez proche pour les cas courants, mais comme des décisions humaines entrent en jeu, la complexité est en pratique infinie et il peut falloir un logiciel sur mesure pour obtenir le bon résultat
    Aujourd’hui, si l’on ne peut pas garantir qu’on reste limité aux 2⁷ premiers points de code Unicode, l’offset 0b0010_0000 casse facilement, et un chemin de code plus coûteux qu’une simple opération sur un bit devient inévitable. Même dans CPython, ''.upper passe par le chemin rapide ASCII de unicode_upper{,_impl}, mais en pratique il effectue une branche, une fonction inline, un accès à une structure, plusieurs fonctions et fonctions, des macros et finit par faire une consultation de table
    Les implémentations de ''.upper dans les langages modernes ont en général une complexité comparable, et même si le compilateur optimise, l’approche moderne semble inévitablement plus coûteuse qu’une unique opération sur un bit. L’intérêt de cette conception a surtout des chances de ressortir dans des logiciels sur mesure, par exemple quand on fait des opérations arithmétiques sur des données binaires brutes ou sur un numpy.ndarray en dtype=uint8 contenant des données Unicode limitées à des points de code inférieurs ou égaux à 2⁷
    Ce qui m’intrigue, c’est ceci : si l’on suppose que la seule motivation de ce choix de conception est bien celle avancée dans l’article, il existait déjà à l’époque une alternative sous la forme d’une table de correspondance de 128 octets ; à quel moment de l’histoire de l’informatique une opération sur un seul bit a-t-elle été plus efficace ou plus réalisable qu’une consultation dans une table de 128 octets ?

    • Quand on creuse des technologies de masse qui ont longtemps réussi, on tombe souvent sur ce genre de bit tricks
      Un choix de conception fait à un moment donné fige ensuite la forme de quelque chose en s’appuyant sur des hypothèses qui finiront par casser, rendant les extensions ultérieures difficiles voire impossibles. Des articles récents sur IPv6 montrent aussi que certains choix faits à l’époque, pertinents pour IPv4 et IPv6, sont aujourd’hui devenus un fardeau énorme
      Dans une lecture bienveillante, ce genre de choix résulte d’un arbitrage entre risques et bénéfices. Quelque chose comme : « une simple opération sur un bit rend la conversion d’un caractère en majuscule bien plus rapide, donc cela vaut le risque qu’un jour ça casse quand des utilisateurs de japonais arriveront »
      Vu depuis aujourd’hui, un choix fait en 1976 complique la vie en 2026 ; mais si l’on n’avait pas d’ordinateur en 1976, on n’a pas profité de l’avantage et il ne reste que les inconvénients. Si l’on met ce sentiment d’égoïsme de côté, je me demande comment mieux prévoir la durabilité de ce type de choix quand on conçoit un logiciel supposé rester populaire encore 20 ans plus tard
    • Beaucoup de protocoles réseau utilisent encore un encodage insensible à la casse limité à l’ASCII, donc un tolower rapide au niveau bit reste utile. https://dotat.at/@/2022-06-27-tolower-swar.html
  • Au moins en ASCII, les lettres restent contiguës. En EBCDIC, l’écart est de 0x40 (64) et, comparé à l’ASCII, cela ressemble à deux lignes de 9 caractères et une ligne de 8 caractères empilées verticalement
    https://en.wikipedia.org/wiki/EBCDIC

  • Ça me rappelle que les pseudonymes IRC étaient insensibles à la casse ASCII, donc foo{ était identique à foo[ et bar| à bar\
    Je ne serais pas surpris que certains clients soient encore perturbés par ça