Я решал некоторые проблемы с codeforces. Обычно я сначала проверяю, является ли символ верхней или нижней английской буквой, затем вычитаю или добавляю, 32
чтобы преобразовать его в соответствующую букву. Но я нашел, что кто-то ^= 32
делает то же самое. Вот:
char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a
Я искал объяснение этому и не узнал. Так почему это работает?
c++
bit-manipulation
ascii
Девон
источник
источник
@
в `с помощью^ 32
.toupper
иtolower
для переключения регистров.A
вZ
. Это хорошо, если вы заботитесь только об английском языке (и не используете написание «наивный», такие слова, как «café» или имена с диакритическими знаками ...), но мир - это не только английский.Ответы:
Давайте посмотрим на таблицу кодов ASCII в двоичном виде.
И 32 -
0100000
это единственная разница между строчными и прописными буквами. Так что переключение этого бита переключает регистр букв.источник
{
короче[
, поэтому это «нижний» регистр. Нет? Хорошо, я покажу себя: Dfoobar[]
иfoobar{}
являются одинаковыми, поскольку псевдонимы не чувствительны к регистру , а IRC берет свое начало в Скандинавии :)Это использует тот факт, что значения ASCII были выбраны действительно умными людьми.
Это переворачивает 6-й младший бит 1 из
foo
(флаг верхнего регистра типа ASCII), преобразуя верхний регистр ASCII в нижний регистр и наоборот .пример
И благодаря свойству XOR
'a' ^ 32 == 'A'
.уведомление
C ++ не требуется использовать ASCII для представления символов. Другой вариант - EBCDIC . Этот прием работает только на платформах ASCII. Более переносимым решением было бы использовать
std::tolower
иstd::toupper
, с учетом предлагаемого бонуса, быть осведомленным о локали (хотя это не решает автоматически все ваши проблемы, см. Комментарии):1) Поскольку 32 равно
1 << 5
(2 степени 5), оно переворачивает 6-й бит (считая от 1).источник
tolower
в немецком не просто нуждается в словаре, оно должно уметь анализировать значение.Позвольте мне сказать, что это - хотя это кажется умным - действительно, действительно глупый взлом. Если кто-то порекомендует это вам в 2019 году, поразите его. Ударь его так сильно, как только сможешь.
Конечно, вы можете сделать это в своем собственном программном обеспечении, которое вы и никто другой не используете, если вы знаете, что вы никогда не будете использовать какой-либо язык, кроме английского. В противном случае не идти.
Взлом был спорным «ОК» около 30-35 лет назад, когда компьютеры на самом деле мало что делали, кроме английского в ASCII и, возможно, одного или двух основных европейских языков. Но ... уже не так.
Хак работает, потому что верхний и нижний регистр США-латиницы точно
0x20
отделены друг от друга и отображаются в одном и том же порядке, что является лишь одним отличием. Который, на самом деле, этот бит взломать, переключает.Теперь люди, создающие кодовые страницы для Западной Европы, а затем и консорциум Unicode, были достаточно умны, чтобы сохранить эту схему, например, для немецких умлаутов и гласных с французским акцентом. Это не так, поскольку (пока кто-то не убедил консорциум Unicode в 2017 году, и об этом не написал большой печатный журнал Fake News, на самом деле убедив Duden - без комментариев) , даже не существует как версаль (трансформируется в SS) , Теперь он действительно существует как версальна, но две
0x1DBF
позиции друг от друга, а не0x20
.Однако разработчики были недостаточно внимательны, чтобы продолжать. Например, если вы примените свой хак на некоторых восточноевропейских языках или тому подобное (я бы не знал о кириллице), вы получите неприятный сюрприз. Все эти символы «топорик» являются примерами того, что строчные и прописные - один за другим. Таким образом, взлом не работает должным образом.
Есть еще много вопросов, которые нужно учитывать, например, некоторые символы не просто преобразуются из строчных в верхние (они заменяются различными последовательностями), либо они могут изменить форму (требуя разных кодовых точек).
Даже не думайте о том, что этот хак сделает с такими вещами, как тайский или китайский (это просто даст вам полную чушь).
Сохранение нескольких сотен циклов ЦП могло бы быть очень полезным 30 лет назад, но в настоящее время действительно нет оправдания для правильного преобразования строки. Существуют библиотечные функции для выполнения этой нетривиальной задачи.
Время , необходимое для преобразования нескольких десятков килобайт текста должным образом в настоящее время незначительно.
источник
Это работает, потому что, как это бывает, разница между 'a' и A 'в ASCII и производных кодировках составляет 32, а 32 также является значением шестого бита. Переключение 6-го бита с исключительным ИЛИ, таким образом, преобразует между верхним и нижним.
источник
Скорее всего, ваша реализация набора символов будет ASCII. Если мы посмотрим на таблицу:
Мы видим, что есть разница
32
между значением строчных и прописных чисел. Следовательно, если мы это сделаем^= 32
(что равняется переключению 6-го младшего значащего бита), он меняется между строчными и прописными буквами.Обратите внимание, что он работает со всеми символами, а не только с буквами. Он переключает символ с соответствующим символом, где 6-й бит отличается, в результате чего получается пара символов, которые переключаются между ними. Для букв соответствующие прописные / строчные буквы образуют такую пару. А
NUL
изменится наSpace
и наоборот, и@
переключится с обратной чертой. В основном любой символ в первом столбце на этой диаграмме переключается с символом на один столбец выше, и то же самое относится к третьему и четвертому столбцам.Я бы не стал использовать этот хак, поскольку нет гарантии, что он будет работать на любой системе. Просто используйте взамен toupper и tolower и такие запросы, как isupper .
источник
32 ^ 32
это 0, а не 64[a-z]
и[A-Z]
есть "буквы". Остальные совпадения, которые следуют тому же правилу. Если бы кто-то попросил вас «прописными буквами», что бы это было? это все равно будет "]" - "}" не "верхний регистр" из "]".%32
«выравнивания» в системе кодирования ASCII. Вот почему бит0x20
- единственное различие между версиями одной и той же буквы в верхнем / нижнем регистре. Если бы это было не так, вам нужно было бы добавлять или вычитать0x20
, а не просто переключать, и для некоторых букв было бы выполнено переворачивание других старших бит. (И та же самая операция не могла переключаться, и проверка буквенных символов в первую очередь была бы более сложной, потому что вы не могли|= 0x20
заставить lcase.)Здесь много хороших ответов, которые описывают, как это работает, но почему это работает, так это для повышения производительности. Побитовые операции выполняются быстрее, чем большинство других операций внутри процессора. Вы можете быстро выполнить сравнение без учета регистра, просто не глядя на бит, который определяет регистр, или измените регистр на верхний / нижний, просто перевернув бит (те ребята, которые разработали таблицу ASCII, были довольно умны).
Очевидно, что сегодня это не так важно, как это было в 1960 году (когда впервые началась работа над ASCII), из-за более быстрых процессоров и Unicode, но все еще есть некоторые недорогие процессоры, которые могут существенно изменить ситуацию. до тех пор, пока вы можете гарантировать только символы ASCII.
https://en.wikipedia.org/wiki/Bitwise_operation
ПРИМЕЧАНИЕ. Я бы рекомендовал использовать стандартные библиотеки для работы со строками по ряду причин (удобочитаемость, корректность, переносимость и т. Д.). Используйте переворот только в том случае, если вы измерили производительность, и это ваше узкое место.
источник
Вот как работает ASCII, вот и все.
Но используя это, вы отказываетесь от переносимости, поскольку C ++ не настаивает на ASCII в качестве кодировки.
Вот почему функции
std::toupper
иstd::tolower
реализованы в стандартной библиотеке C ++ - вы должны использовать их вместо этого.источник
См. Вторую таблицу по адресу http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii и следующие примечания, воспроизведенные ниже:
ASCII был разработан таким образом, чтобы shift и ctrlклавиши клавиатуры могут быть реализованы без особой (или , возможно , какой - либо для ctrl) логики - shiftвероятно , требуется всего лишь несколько ворот. Вероятно, имеет смысл хранить как минимум такой же проводной протокол, как и любую другую кодировку символов (никакого программного преобразования не требуется).
Связанная статья также объясняет много странных соглашений хакеров, таких как
And control H does a single character and is an old^H^H^H^H^H classic joke.
( найденный здесь ).источник
foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20
, хотя это только ASCII и, следовательно, неразумно по причинам, указанным в других ответах. Это, вероятно, также может быть улучшено без программирования.foo ^= 0x20 >> !(foo & 0x40)
было бы проще. Также хороший пример того, почему краткий код часто считается нечитаемым ^ _ ^.Xoring с 32 (00100000 в двоичном формате) устанавливает или сбрасывает шестой бит (справа). Это строго эквивалентно сложению или вычитанию 32.
источник
Буквенные диапазоны в нижнем и верхнем регистре не пересекают границу
%32
«выравнивания» в системе кодирования ASCII.Вот почему бит
0x20
- единственное различие между версиями одной и той же буквы в верхнем / нижнем регистре.Если бы это было не так, вам нужно было бы добавлять или вычитать
0x20
, а не просто переключать, и для некоторых букв было бы выполнено переворачивание других старших бит. (И не было бы ни одной операции, которая могла бы переключаться, и проверка буквенных символов в первую очередь была бы более сложной, потому что вы не могли | = 0x20 заставить lcase.)Связанные трюки только для ASCII: вы можете проверить алфавитный символ ASCII , введя строчные буквы с,
c |= 0x20
а затем проверив, если (без знака)c - 'a' <= ('z'-'a')
. Так что всего 3 операции: ИЛИ + SUB + CMP против постоянной 25. Конечно, компиляторы знают, как оптимизировать(c>='a' && c<='z')
в asm, как это для вас , поэтому самое большее вы должны выполнитьc|=0x20
сами. Довольно неудобно выполнять все необходимые кастинги самостоятельно, особенно для работы с целочисленными акциями по умолчанию для подписанныхint
.См. Также Преобразование строки в C ++ в верхний регистр (SIMD-строка
toupper
только для ASCII, маскировка операнда для XOR с использованием этой проверки.)А также Как получить доступ к массиву символов и изменить строчные буквы на прописные, и наоборот (C с внутренними SIMD и скалярный x86 asm case-flip для буквенных символов ASCII, оставляя другие без изменений.)
Эти приемы в основном полезны только при ручной оптимизации некоторой обработки текста с помощью SIMD (например, SSE2 или NEON), после проверки того, что ни один из
char
s в векторе не установлен старший бит. (И, таким образом, ни один из байтов не является частью многобайтовой кодировки UTF-8 для одного символа, который может иметь различные обратные символы верхнего / нижнего регистра). Если вы найдете что-либо, вы можете вернуться к скаляру для этого фрагмента из 16 байтов или для остальной части строки.Есть даже некоторые места, где
toupper()
илиtolower()
на некоторых символах в диапазоне ASCII производят символы вне этого диапазона, особенно турецкие, где I ↔ ı и İ ↔ i. В этих локалях вам понадобится более сложная проверка, или, возможно, вы вообще не будете пытаться использовать эту оптимизацию.Но в некоторых случаях вам разрешено использовать ASCII вместо UTF-8, например, утилиты Unix с
LANG=C
(локаль POSIX), а не что-en_CA.UTF-8
либо еще.Но если вы можете убедиться, что это безопасно, вы можете выполнять
toupper
строки средней длины намного быстрее, чем вызыватьtoupper()
в цикле (например, 5x), и последнее, что я тестировал с Boost 1.58 , намного быстрее, чемboost::to_upper_copy<char*, std::string>()
глупостьdynamic_cast
для каждого символа.источник