В чем идея ^ = 32, которая преобразует строчные буквы в прописные и наоборот? - PullRequest
0 голосов
/ 05 февраля 2019

Я решал некоторую проблему с codeforces.Обычно я сначала проверяю, является ли символ верхней или нижней английской буквой, затем вычитаю или добавляю 32, чтобы преобразовать его в соответствующую букву.Но я нашел, что кто-то делает ^= 32, чтобы сделать то же самое.Вот оно:

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

Я искал объяснение этому и не нашел.Так почему это работает?

Ответы [ 10 ]

0 голосов
/ 08 февраля 2019

Буквенные диапазоны в нижнем и верхнем регистре не пересекают границу «выравнивания» %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.

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

См. Также Преобразование строки в C ++ в верхний регистр (SIMD строка toupper только для ASCII, маскирующая операнд для XOR с помощью этой проверки.)

А также Как получить доступ к массиву символов и изменить строчные буквы на прописные, и наоборот (C со встроенными SIMD и скалярный x86 asm case-flip для буквенных символов ASCII, оставляя другие без изменений.)


Эти приемы в основном полезны только при ручной оптимизации какой-либо обработки текста с помощью SIMD (например, SSE2 или NEON), после проверки того, что ни у одного из char в векторе не установлен их старший бит.(И, таким образом, ни один из байтов не является частью многобайтовой кодировки 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 для каждого символа.

0 голосов
/ 06 февраля 2019

Позвольте мне сказать, что это - хотя это кажется умным - действительно, действительно глупый взлом.Если кто-то порекомендует это вам в 2019 году, поразите его.Ударь его так сильно, как только сможешь.
Конечно, вы можете сделать это в своем собственном программном обеспечении, которое вы и никто другой не используете, если знаете, что вы никогда не будете использовать какой-либо язык, кроме английского.В противном случае ничего не выйдет.

Хак был спорным "ОК" около 30-35 лет назад, когда компьютеры на самом деле мало что делали, кроме английского в ASCII, и возможно один или два основных европейскихязыки.Но ... уже не так.

Хак работает, потому что верхний и нижний регистр США-латиницы точно 0x20 отделены друг от друга и отображаются в одинаковом порядке, что является лишь одним отличием.Который, собственно, этот хак взламывает.

Теперь люди, создающие кодовые страницы для Западной Европы, а затем и консорциум Unicode, были достаточно умны, чтобы сохранить эту схему, например, для немецких умлаутов и гласных с французским акцентом.,Не так для ß, который (до тех пор, пока кто-то не убедил консорциум Unicode в 2017 году, и об этом не написал большой печатный журнал Fake News, на самом деле убедивший Дуден - без комментариев) даже не существует какВерсаль (превращается в СС).Теперь он существует как версаль, но эти два находятся на 0x1DBF позициях друг от друга, а не 0x20.

Однако разработчики были не достаточно внимательными, чтобыпродолжай в том же духе.Например, если вы примените свой хак на некоторых восточноевропейских языках и т. П. (Я бы не знал о кириллице), вас ждет неприятный сюрприз.Все эти символы «топорик» являются примерами того, что строчные и прописные - один за другим.Таким образом, хак не работает должным образом.

Есть еще много вопросов, которые нужно учитывать, например, некоторые символы вообще не просто преобразуются из нижнего в верхний регистр (их заменяют другимипоследовательности), или они могут изменить форму (требуя разные кодовые точки).

Даже не думайте о том, что этот хак сделает с такими вещами, как тайский или китайский (это просто даст вам полную чушь).

Сохранение нескольких сотен циклов ЦП могло бы быть очень полезным 30 лет назад, но в настоящее время действительно нет оправдания для правильного преобразования строки.Существуют библиотечные функции для выполнения этой нетривиальной задачи.
Время, необходимое для правильного преобразования нескольких десятков килобайт текста в настоящее время ничтожно мало.

0 голосов
/ 07 февраля 2019

См. Вторую таблицу на http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii, и следующие примечания, воспроизведенные ниже:

Модификатор Control на вашей клавиатуре в основном очищает три верхних бита любого введенного вами символа, оставляянижняя пятерка и отображение его в диапазоне 0.31.Так, например, Ctrl-SPACE, Ctrl- @ и Ctrl-`все означают одно и то же: NUL.

Очень старые клавиатуры, используемые для Shift, просто переключая 32 или 16 бит, в зависимости отключ;Вот почему отношения между маленькими и заглавными буквами в ASCII настолько регулярны, а отношения между цифрами и символами, а также некоторыми парами символов, являются регулярными, если вы щуритесь на это.ASR-33, который был полностью заглавным терминалом, даже позволял вам генерировать некоторые знаки пунктуации, для которых у него не было ключей, сдвигая 16-битный код;таким образом, например, Shift-K (0x4B) стал [(0x5B)

ASCII сконструирован таким образом, что клавиши клавиатуры shift и ctrl моглибыть реализованным без особой (или, возможно, какой-либо для ctrl ) логики - shift , вероятно, потребовало всего несколько шлюзов.Возможно, имеет смысл хранить проводной протокол как минимум в таком же смысле, как и любая другая кодировка символов (не требуется никакого программного преобразования).

В связанной статье также объясняется множество странных хакерских соглашений, таких как And control H does a single character and is an old^H^H^H^H^H classic joke. ( найдено здесь ).

0 голосов
/ 05 февраля 2019

Xoring с 32 (00100000 в двоичном формате) устанавливает или сбрасывает шестой бит (справа).Это строго эквивалентно добавлению или вычитанию 32.

0 голосов
/ 06 февраля 2019

Здесь много хороших ответов, которые описывают, как это работает, но почему это работает, так это для повышения производительности.Побитовые операции выполняются быстрее, чем большинство других операций внутри процессора.Вы можете быстро выполнить сравнение без учета регистра, просто не глядя на бит, который определяет регистр, или измените регистр на верхний / нижний, просто перевернув бит (те ребята, которые разработали таблицу ASCII, были довольно умными).

Очевидно,Сегодня это не так важно, как это было в 1960 году (когда впервые началась работа над ASCII) из-за более быстрых процессоров и Unicode, но все еще есть некоторые недорогие процессоры, которые могут иметь существенное значение, посколькуПока вы можете гарантировать только символы ASCII.

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

На простых недорогих процессорах побитовые операции обычно выполняются значительно быстрее, чем деление, в несколько раз быстрее, чем умножение, а иногда значительно быстрее, чем сложение.

ПРИМЕЧАНИЕ. Я бы рекомендовал использовать стандартные библиотеки для работы со строками по ряду причин (удобочитаемость, корректность, переносимость и т. Д.).Используйте переворачивание битов, только если вы измерили производительность, и это ваше узкое место.

0 голосов
/ 05 февраля 2019

Вот как работает ASCII, вот и все.

Но, эксплуатируя это, вы отказываетесь от переносимости , поскольку C ++ не настаивает на ASCII в качестве кодировки.

Вот почему функции std::toupper и std::tolower реализованы в стандартной библиотеке C ++ - вы должны использовать их вместо этого.

0 голосов
/ 05 февраля 2019

Используется тот факт, что значения ASCII были выбраны действительно умными людьми.

foo ^= 32;

Это отображает 6-й младший бит 1 из foo(верхний регистр ASCII-типа), преобразующий верхний регистр ASCII в нижний регистр и наоборот .

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

Пример

'A' ^ 32

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

Ипо свойству XOR 'a' ^ 32 == 'A'.

Обратите внимание

C ++ не требуется использовать ASCII для представления символов.Другой вариант - EBCDIC .Этот прием работает только на платформах ASCII.Более переносимым решением было бы использовать std::tolower и std::toupper, с предложенным бонусом, чтобы быть в курсе локали (хотя это не решает автоматически все ваши проблемы, хотя, см. Комментарии):

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) Поскольку 32 равно 1 << 5 (от 2 до 5), оно переворачивает 6-й бит (считая от 1).

0 голосов
/ 05 февраля 2019

Давайте посмотрим на таблицу кодов ASCII в двоичном виде.

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

И 32 - это 0100000, что является единственной разницей между строчными и заглавными буквами.Так что переключение этого бита переключает регистр букв.

0 голосов
/ 05 февраля 2019

Скорее всего, ваша реализация набора символов будет ASCII.Если мы посмотрим на таблицу:

enter image description here

Мы увидим, что есть разница ровно 32 между значением строчных и прописных чисел.Поэтому, если мы сделаем ^= 32 (что соответствует переключению 6-го младшего значащего бита), он будет меняться между строчными и прописными буквами.

Обратите внимание, что он работает со всеми символами, а не только с буквами.Он переключает символ с соответствующим символом, где 6-й бит отличается, в результате чего получается пара символов, которые переключаются между ними.Для букв соответствующие прописные / строчные буквы образуют такую ​​пару.NUL изменится на Space и наоборот, а @ переключится с обратной чертой.По сути, любой символ в первом столбце на этой диаграмме переключается с символом на один столбец выше, и то же самое относится к третьему и четвертому столбцам.

Я бы не использовал этот хак, поскольку нет гарантии, что онсобирается работать в любой системе.Просто используйте взамен toupper и tolower и такие запросы, как isupper .

0 голосов
/ 05 февраля 2019

Это работает, потому что, как это бывает, разница между 'a' и A 'в ASCII и производных кодировках составляет 32, а 32 также является значением шестого бита.Переключение 6-го бита с помощью исключающего ИЛИ, таким образом, преобразует верхний и нижний.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...