- No ASCII,
Z está em 90 e a em 97; graças aos 6 caracteres entre eles, a diferença de código entre maiúsculas e minúsculas fica exatamente em 32
- Como 32 é
2^5, pares correspondentes como A 65 e a 97 sempre diferem em apenas um bit: 00100000
- Graças a esse arranjo, é possível converter para maiúsculas fazendo AND com o complemento de bits de
32, converter para minúsculas fazendo OR com 32 e inverter entre maiúsculas e minúsculas fazendo XOR com 32
- A posição da letra no alfabeto pode ser obtida fazendo AND com
31 no código do caractere, deixando apenas os 5 bits inferiores; assim, A/a vira 1 e Z/z vira 26
- ASCII é uma codificação de caracteres inicial de 7 bits que representa apenas 128 code points, e os primeiros 128 code points do Unicode usado hoje são idênticos ao ASCII
O arranjo do ASCII e a diferença de 32
- Na tabela ASCII, o valor de código da letra maiúscula
Z é 90, e a minúscula a não está no valor seguinte, mas sim em 97
- Entre elas estão os 6 caracteres
[ \ ] ^ _ `
- O alfabeto inglês tem 26 letras e, somando esses 6 caracteres, temos
26 + 6 = 32
32 corresponde a 2^5, então a relação entre maiúsculas e minúsculas fica alinhada como diferença de um único bit específico
- Por exemplo,
A é 65 e também 01000001, enquanto a é 97 e também 01100001; a diferença entre os dois valores é 32
A relação entre ASCII e Unicode
- ASCII é um dos primeiros métodos de codificação de caracteres e usa apenas 7 bits para representar
2^7 = 128 code points
- Esses 128 code points não bastam para representar todos os caracteres usados por humanos, especialmente línguas como o chinês, que têm dezenas de milhares de caracteres
- Hoje, o conjunto de caracteres padrão é o Unicode, que tem várias codificações, como UTF-8 e UTF-16
- Os primeiros 128 code points do Unicode são idênticos ao ASCII
O 5º bit que separa maiúsculas de minúsculas
- Ao comparar em binário uma letra maiúscula com sua minúscula correspondente, o bit que corresponde a
32 sempre muda
65 = 01000001 = A
97 = 01100001 = a
66 = 01000010 = B
98 = 01100010 = b
67 = 01000011 = C
99 = 01100011 = c
32 em binário é 00100000, e esse único bit é o que cria a diferença entre maiúsculas e minúsculas
- O arranjo das letras no ASCII foi feito para permitir conversões fáceis entre maiúsculas e minúsculas com operações de bits
Tratando maiúsculas e minúsculas com operações de bits
-
Converter para maiúsculas
- Para transformar um caractere em maiúscula, faz-se um AND de bits com o complemento de bits 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')
- Aplicando isso a
a, 97 vira 65, tornando-se A
- Se a mesma operação for aplicada a
A, ela continua sendo A
-
Converter para minúsculas
- Para transformar um caractere em minúscula, faz-se um OR de bits com
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')
- Aplicando isso a
A, 65 vira 97, tornando-se a
- Se a mesma operação for aplicada a
a, ela continua sendo a
-
Inverter maiúsculas e minúsculas
- Para inverter entre maiúsculas e minúsculas, faz-se um XOR de bits com
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')
Obtendo a posição da letra com os 5 bits inferiores
- A posição da letra no alfabeto pode ser obtida fazendo um AND de bits com
31 no código do caractere
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 em binário é 00011111, então ele apaga os bits mais altos e deixa apenas os 5 bits inferiores
- No ASCII, os 5 bits inferiores das letras coincidem com a posição delas no alfabeto
A/a termina em 00001, então vira 1; B/b termina em 00010, então vira 2; e Z/z termina em 11010, então vira 26
- Em códigos de caractere ASCII,
c & 31 é igual a c % 32
- Como
32 é uma potência de 2, mascarar com 31 remove os grupos de 32 e preserva apenas o restante
'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 comentários
Opiniões no Lobste.rs
A explicação é boa, mas tenho a impressão de que https://garbagecollected.org/2017/01/31/four-column-ascii/ explica melhor
Não é só uma questão de Shift; Ctrl também está envolvido. Por exemplo, Tab é Ctrl-I, porque I é
1001001e Ctrl mascara o primeiro bit, deixando o0001001do TabSe você estiver usando lógica eletromecânica em vez de lógica eletrônica, como os fabricantes dos anos 1960, um bit paired keyboard é muito mais fácil de implementar
A tecla Shift só precisa alternar um único bit no caractere ASCII. Hoje colocamos uma CPU genérica em todo teclado e a lógica sai praticamente de graça, mas na época em que um computador de uso geral ocupava uma sala inteira, esse tipo de solução era muito mais caro
O texto apresenta, como motivação para o arranjo do ASCII, a possibilidade de implementar conversão entre maiúsculas e minúsculas de forma eficiente com operações de bits, mas esse valor parece bem pequeno hoje em dia, ainda que historicamente possa ter sido relevante
A conversão
a→Asó funciona em texto simples, e mesmo que ISO-8859-1 ou Unicode estendam parcialmente esse arranjo para caracteres comoüeç, a conversão de maiúsculas e minúsculas varia conforme localidade, contexto e época. A correspondência correta de maiúsculas/minúsculas paraßouïdepende do idioma, de órgãos reguladores, da região de uso e do período históricoO Unicode fornece tabelas de mapeamento e folding, como https://www.unicode.org/charts/case/, que dão uma resposta razoavelmente boa para os casos comuns, mas como isso envolve políticas humanas, a complexidade é praticamente infinita e talvez seja necessário software sob medida para acertar de verdade
Hoje, se você não puder garantir que está restrito aos primeiros 2⁷ code points do Unicode, o deslocamento
0b0010_0000quebra facilmente, e inevitavelmente entra um caminho de código mais caro do que uma única operação de bit. Até no CPython,''.upperpassa pelo caminho rápido ASCII deunicode_upper{,_impl}, mas na prática ele faz branch, função inline, acesso a struct, várias funções e funções, macros e faz uma busca em tabelaImplementações de
''.upperem linguagens modernas provavelmente têm um nível parecido de complexidade e, mesmo com otimizações do compilador, uma abordagem moderna parece inevitavelmente mais cara do que uma única operação de bit. A vantagem desse design provavelmente só se destaca em software sob medida, como fazer operações aritméticas sobre umnumpy.ndarraycomdtype=uint8contendo binário bruto ou dados Unicode limitados a code points abaixo de 2⁷O que me deixa curioso é isto: se assumirmos que a única motivação dessa escolha de design foi a citada no texto, também existia na época a alternativa de uma tabela de consulta de 128 bytes; em que período da história da computação uma única operação de bit foi mais eficiente ou mais viável do que consultar uma tabela de 128 bytes?
Uma escolha de design feita em certo momento acaba fixando a forma de alguma coisa com base em suposições que mais tarde quebram, e isso torna expansões futuras difíceis ou impossíveis. Artigos recentes sobre IPv6 também mostram como escolhas que faziam sentido na época do IPv4 e do IPv6 acabaram virando um peso enorme hoje
Numa leitura generosa, essas escolhas devem ter sido feitas ponderando riscos e ganhos. Algo como: “se der para transformar um caractere em maiúsculo com uma única operação de bit, isso será muito mais rápido, então vale aceitar o risco de quebrar no dia em que surgirem usuários de japonês”
Do ponto de vista atual, uma escolha de 1976 torna a vida em 2026 mais difícil, mas se você nunca teve um computador em 1976, não recebeu o benefício e ficou só com a desvantagem. Tentando deixar esse egoísmo de lado, fico curioso sobre como prever melhor a sustentabilidade desse tipo de escolha ao fazer software que ainda possa ser popular dali a 20 anos
tolowerrápido em nível de bits ainda é útil. https://dotat.at/@/2022-06-27-tolower-swar.htmlPelo menos no ASCII, as letras são contíguas. EBCDIC tem um intervalo de
0x40(64) e, em comparação com o ASCII, parece duas linhas de 9 caracteres e uma linha de 8 empilhadas verticalmentehttps://en.wikipedia.org/wiki/EBCDIC
Melhor nem começar a falar do código de 5 bits http://www.quadibloc.com/crypto/images/tele38.gif nem de certos periféricos da CDC que, se bem me lembro, usavam um código de 6 bits cheio de caracteres de controle de avanço de página
Lembro que apelidos no IRC eram ASCII case-insensitive, então
foo{era igual afoo[ebar|era igual abar\Não me surpreenderia se alguns clientes ainda se confundissem por causa disso