Estaba resolviendo algún problema en las fuerzas de código. Normalmente, primero verifico si el carácter es una letra en inglés superior o inferior y luego resto o sumo 32
para convertirlo a la letra correspondiente. Pero encontré que alguien hace ^= 32
lo mismo. Aquí está:
char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a
He buscado una explicación para esto y no lo descubrí. Entonces, ¿por qué esto funciona?
c++
bit-manipulation
ascii
Devon
fuente
fuente
@
a `mediante^ 32
.toupper
ytolower
cambiar entre mayúsculas y minúsculas.A
paraZ
. Eso está bien siempre y cuando solo te importe el inglés (y no uses ortografía "ingenua", palabras como "café" o nombres con signos diacríticos ...), pero el mundo no es solo inglés.Respuestas:
Echemos un vistazo a la tabla de códigos ASCII en binario.
Y 32 es
0100000
la única diferencia entre letras minúsculas y mayúsculas. Así que alternar ese bit alterna el caso de una carta.fuente
{
es más corto que[
, por lo que es un caso "inferior". ¿No? Ok, me mostraré: Dfoobar[]
yfoobar{}
son apodos idénticos, ya que los apodos no distinguen entre mayúsculas y minúsculas , e IRC tiene su origen en Escandinavia :)Esto utiliza el hecho de que los valores ASCII han sido elegidos por personas realmente inteligentes.
Esto voltea el sexto bit 1 más bajo de
foo
(el indicador en mayúsculas de ASCII), transformando una mayúscula ASCII en minúscula y viceversa .Ejemplo
Así como por bienes de XOR,
'a' ^ 32 == 'A'
.aviso
No se requiere C ++ para usar ASCII para representar caracteres. Otra variante es EBCDIC . Este truco solo funciona en plataformas ASCII. Una solución más portátil sería usar
std::tolower
ystd::toupper
, con la bonificación ofrecida para tener en cuenta la ubicación (sin embargo, no resuelve automáticamente todos sus problemas, vea los comentarios):1) Como 32 es
1 << 5
(2 a la potencia 5), cambia el sexto bit (contando desde 1).fuente
tolower
en alemán no solo necesita un diccionario, sino que debe poder analizar el significado.Permítanme decir que esto es, aunque parezca inteligente, un truco muy, muy estúpido. Si alguien te recomienda esto en 2019, golpéalo. Golpéalo tan fuerte como puedas.
Por supuesto, puede hacerlo en su propio software que usted y nadie más usa si sabe que nunca usará ningún idioma que no sea inglés de todos modos. De lo contrario, no te vayas.
El truco fue discutible "OK" hace unos 30-35 años cuando las computadoras realmente no hacían mucho más que inglés en ASCII, y tal vez uno o dos idiomas europeos principales. Pero ... ya no es así.
El truco funciona porque las mayúsculas y minúsculas de EE. UU. Y América están exactamente
0x20
separadas entre sí y aparecen en el mismo orden, lo cual es solo un poco de diferencia. Lo que, de hecho, este pequeño truco, alterna.Ahora, las personas que crean páginas de códigos para Europa occidental, y más tarde el consorcio Unicode, fueron lo suficientemente inteligentes como para mantener este esquema, por ejemplo, para Umlauts alemanes y Vocales con acento francés. No es así para ß que (hasta que alguien convenció al consorcio Unicode en 2017, y una gran revista impresa de Fake News escribió sobre ello, realmente convenció al Duden, sin comentarios al respecto) ni siquiera existe como un versal (se transforma en SS) . Ahora bien, no existe como Versal, pero los dos son
0x1DBF
puestos aparte, no0x20
.Sin embargo, los implementadores no fueron lo suficientemente considerados para mantener esto en marcha. Por ejemplo, si aplica su truco en algunos idiomas de Europa del Este o similares (no sabría sobre cirílico), obtendrá una desagradable sorpresa. Todos esos caracteres "hacha" son ejemplos de eso, minúsculas y mayúsculas son uno aparte. El truco por lo tanto no funciona correctamente allí.
Hay mucho más que considerar, por ejemplo, algunos caracteres no se transforman simplemente de minúsculas a mayúsculas (se reemplazan con diferentes secuencias), o pueden cambiar de forma (lo que requiere diferentes puntos de código).
Ni siquiera pienses en lo que este truco hará para cosas como tailandés o chino (solo te dará una completa tontería).
Ahorrar un par de cientos de ciclos de CPU puede haber valido la pena hace 30 años, pero hoy en día, realmente no hay excusa para convertir una cadena correctamente. Hay funciones de biblioteca para realizar esta tarea no trivial.
El tiempo necesario para convertir varias docenas de kilobytes de texto correctamente es insignificante hoy en día.
fuente
Funciona porque, como sucede, la diferencia entre 'a' y A 'en las codificaciones ASCII y derivadas es 32, y 32 también es el valor del sexto bit. Voltear el sexto bit con un OR exclusivo convierte así entre superior e inferior.
fuente
Lo más probable es que su implementación del conjunto de caracteres sea ASCII. Si miramos la mesa:
Vemos que hay una diferencia exactamente
32
entre el valor de un número en minúsculas y mayúsculas. Por lo tanto, si lo hacemos^= 32
(lo que equivale a alternar el sexto bit menos significativo), cambia entre un carácter en minúsculas y mayúsculas.Tenga en cuenta que funciona con todos los símbolos, no solo con las letras. Alterna un carácter con el carácter respectivo donde el sexto bit es diferente, lo que resulta en un par de caracteres que se alternan de un lado a otro. Para las letras, los respectivos caracteres en mayúscula / minúscula forman dicho par. A
NUL
cambiará aSpace
y al revés, y el@
cambia con la tecla de retroceso. Básicamente, cualquier carácter en la primera columna de este gráfico alterna con el carácter una columna sobre, y lo mismo se aplica a la tercera y cuarta columna.Sin embargo, no usaría este truco, ya que no hay garantía de que funcione en ningún sistema. Simplemente use toupper y tolower en su lugar, y consultas como isupper .
fuente
32 ^ 32
es 0, no 64[a-z]
y[A-Z]
son "letras". El resto son coincidencias que siguen la misma regla. Si alguien le pidiera "mayúscula]", ¿cuál sería? seguiría siendo "]" - "}" no es la "mayúscula" de "]".%32
límite de "alineación" en el sistema de codificación ASCII. Es por eso que bit0x20
es la única diferencia entre las versiones en mayúsculas / minúsculas de la misma letra. Si este no fuera el caso, necesitaría sumar o restar0x20
, no solo alternar, y para algunas letras se realizaría para voltear otros bits más altos. (Y la misma operación no podría alternar, y buscar caracteres alfabéticos en primer lugar sería más difícil porque no se podría|= 0x20
forzar a Lcase)Muchas buenas respuestas aquí que describen cómo funciona esto, pero por qué funciona de esta manera es para mejorar el rendimiento. Las operaciones bit a bit son más rápidas que la mayoría de las demás operaciones dentro de un procesador. Puede hacer rápidamente una comparación entre mayúsculas y minúsculas simplemente no mirando el bit que determina el caso o cambiar el caso a mayúsculas / minúsculas simplemente volteando el bit (los tipos que diseñaron la tabla ASCII eran bastante inteligentes).
Obviamente, esto no es tan importante hoy como lo fue en 1960 (cuando comenzó el trabajo en ASCII) debido a los procesadores más rápidos y Unicode, pero todavía hay algunos procesadores de bajo costo que podrían marcar una diferencia significativa siempre que pueda garantizar solo caracteres ASCII.
https://en.wikipedia.org/wiki/Bitwise_operation
NOTA: recomendaría utilizar bibliotecas estándar para trabajar con cadenas por varias razones (legibilidad, corrección, portabilidad, etc.). Solo use el cambio de bits si ha medido el rendimiento y este es su cuello de botella.
fuente
Así es como funciona ASCII, eso es todo.
Pero al explotar esto, estás renunciando a la portabilidad ya que C ++ no insiste en ASCII como codificación.
Esta es la razón por la cual las funciones
std::toupper
ystd::tolower
se implementan en la biblioteca estándar de C ++: en su lugar, debe usarlas.fuente
Vea la segunda tabla en http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii , y las siguientes notas, reproducidas a continuación:
ASCII se diseñó de tal manera que las teclas shifty del ctrlteclado se pudieran implementar sin mucha ctrllógica (o tal vez ninguna ), shiftprobablemente solo requirieron unas pocas puertas. Probablemente tenía al menos tanto sentido almacenar el protocolo de cable como cualquier otra codificación de caracteres (no se requiere conversión de software).
El artículo vinculado también explica muchas convenciones de hackers extrañas como
And control H does a single character and is an old^H^H^H^H^H classic joke.
(que se encuentra aquí ).fuente
foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20
, aunque esto es solo ASCII y, por lo tanto, imprudente por las razones indicadas en otras respuestas. Probablemente también se pueda mejorar con una programación sin ramificación.foo ^= 0x20 >> !(foo & 0x40)
sería más simple. También es un buen ejemplo de por qué el código breve a menudo se considera ilegible ^ _ ^.Xoring con 32 (00100000 en binario) establece o restablece el sexto bit (desde la derecha). Esto es estrictamente equivalente a sumar o restar 32.
fuente
Los rangos alfabéticos en minúsculas y mayúsculas no cruzan un
%32
límite de "alineación" en el sistema de codificación ASCII.Es por eso que bit
0x20
es la única diferencia entre las versiones en mayúsculas / minúsculas de la misma letra.Si este no fuera el caso, necesitaría sumar o restar
0x20
, no solo alternar, y para algunas letras se realizaría para voltear otros bits más altos. (Y no habría una sola operación que pudiera alternar, y buscar caracteres alfabéticos en primer lugar sería más difícil porque no podría | = 0x20 forzar lcase).Trucos relacionados solo con ASCII: puede verificar si hay un carácter ASCII alfabético forzando minúsculas con
c |= 0x20
y luego verificando si (sin signo)c - 'a' <= ('z'-'a')
. Entonces, solo 3 operaciones: OR + SUB + CMP contra una constante 25. Por supuesto, los compiladores saben cómo optimizar(c>='a' && c<='z')
en asm como este para usted , por lo que a lo sumo debe hacer lac|=0x20
parte usted mismo. Es bastante inconveniente hacer todo el casting necesario usted mismo, especialmente para evitar promociones enteras predeterminadas para firmarint
.Consulte también Convertir una cadena en C ++ a mayúsculas (cadena SIMD
toupper
solo para ASCII, enmascarando el operando para XOR usando esa verificación).Y también Cómo acceder a una matriz de caracteres y cambiar las letras minúsculas a mayúsculas, y viceversa (C con intrínsecos SIMD y mayúscula escalar x86 asm para caracteres alfabéticos ASCII, dejando otros sin modificar).
La mayoría de estos trucos solo son útiles si se optimiza manualmente el procesamiento de texto con SIMD (por ejemplo, SSE2 o NEON), después de comprobar que ninguno de los
char
s en un vector tiene su bit alto establecido. (Y, por lo tanto, ninguno de los bytes forma parte de una codificación UTF-8 de varios bytes para un solo carácter, que podría tener diferentes inversas en mayúsculas / minúsculas). Si encuentra alguno, puede recurrir al escalar para este fragmento de 16 bytes, o para el resto de la cadena.Incluso hay algunas configuraciones regionales donde
toupper()
otolower()
en algunos caracteres en el rango ASCII producen caracteres fuera de ese rango, especialmente en turco donde I ↔ ı e İ ↔ i. En esos entornos locales, necesitaría una verificación más sofisticada, o probablemente no intente utilizar esta optimización en absoluto.Pero en algunos casos, se le permite asumir ASCII en lugar de UTF-8, por ejemplo, utilidades Unix con
LANG=C
(la ubicación POSIX), noen_CA.UTF-8
o lo que sea.Pero si puede verificar que es seguro, puede hacer
toupper
cadenas de longitud media mucho más rápido que llamartoupper()
en un bucle (como 5x), y la última vez que probé con Boost 1.58 , mucho más rápido de loboost::to_upper_copy<char*, std::string>()
que hace una estupidezdynamic_cast
por cada personaje.fuente