Яка ідея ^ = 32, яка перетворює малі літери на верхні і назад?


146

Я вирішував якусь проблему на кодових силах. Зазвичай я спочатку перевіряю, чи є символ верхній або нижній англійською літерою, а потім віднімаю або додаю, 32щоб перетворити його у відповідну букву. Але я знайшов когось ^= 32зробити те саме. Ось:

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

Я шукав пояснення цьому і не дізнався. То чому це працює?


5
en.wikipedia.org/wiki/File:USASCII_code_chart.png Порада: ви можете перетворити @в `за допомогою ^ 32.
KamilCuk

112
FWIW, це насправді не "працює". Він працює для цього конкретного набору символів, але є й інші набори, де його не потрібно використовувати toupperта tolowerперемикати регістри.
NathanOliver

7
колись із онлайн-конкурсів "ідея" полягає в тому, щоб написати код таким заплутаним способом, щоб він ніколи не проходив серйозного огляду;)
idclev 463035818

21
^ = перетворює значення за допомогою XOR. Великі літери ASCII мають нуль у відповідному біті, а малі літери - один. Це сказав, будь ласка, не треба! Використовуйте правильні підпрограми символів (unicode) для перетворення між малі та великі регістри. Ера якраз ASCII давно минула.
Ганс-Мартін Моснер

14
Це не просто те, що він працює лише з деякими наборами символів. Навіть якщо ми припустимо, що весь світ є UTF-8 (що, принаймні, може бути приємною утопічною метою), він також працює лише з 26 листів Aдо Z. Це добре, якщо ви дбаєте лише про англійську мову (і не використовуєте написання "наївно", слова типу "кафе" чи назви з діакритикою ...), але світ - це не лише англійська.
ilkkachu

Відповіді:


149

Давайте розглянемо таблицю коду ASCII у двійковій формі.

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

І 32 - 0100000це єдина різниця між малими та великими літерами. Таким чином, перемикання цього біта перемикає регістр листа.


49
"перемикає випадок" * лише для ASCII
Mooing Duck

39
@Mooing лише для A-Za-z в ASCII. Нижній регістр "[" не є "{".
dbkk

21
@dbkk {коротший [, тому це "нижній" випадок. Немає? Гаразд, я покажу себе: D
Пітер Бадіда

25
Дрібничка: у 7-бітовій області німецькі комп’ютери були перестановлені на ÄÖÜäöü, оскільки нам потрібні Umlauts більше, ніж ці символи, тому в цьому контексті {(ä) насправді був малим регістром [(Ä).
Guntram Blohm підтримує Моніку

14
@GuntramBlohm Подальші дрібниці, ось чому IRC-сервери розглядають foobar[] іfoobar{} ідентичними прізвиськами, так як псевдоніми не чутливі до регістру , а IRC бере свій початок у Скандинавії :)
ZeroKnight

117

Тут використовується той факт, що значення 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).


16
EBCDIC також вибрали дуже розумні люди: дуже добре працює на перфокартах пор. ASCII - це безлад. Але це приємна відповідь, +1.
Вірсавія

65
Я не знаю , про перфокартах, але ASCII був використаний на паперовій стрічці. Ось чому символ Delete кодується як 1111111: Таким чином, ви можете позначити будь-який символ як "видалений", пробиваючи всі отвори в його стовпці на стрічці.
dan04

23
@Bathsheba як хтось, хто не використовував перфокарт, дуже важко обернути голову думкою про те, що EBCDIC був розумно розроблений.
Lord Farquaad

9
@LordFarquaad IMHO Картина у Вікіпедії, як пишуться листи на перфокарті, є наочною ілюстрацією того, як EBCDIC має певний (але не загальний, див. / Проти S) сенс для цього кодування. en.wikipedia.org/wiki/EBCDIC#/media / ...
Петеріс

11
@ dan04 Зауважте, що слід зазначити "яка нижча форма" MASSE "?". Для тих, хто не знає, є два слова німецькою мовою, у яких велика форма букви MASSE; один - "Массе", а другий - "Масе". Правильна tolowerнімецька мова не потребує просто словника, вона повинна вміти розбирати значення.
Мартін Боннер підтримує Моніку

35

Дозвольте мені сказати, що це - хоча це здається розумним - справді, справді дурний хакер. Якщо хтось рекомендує це вам у 2019 році, ударіть його. Удар його як можна сильніше.
Ви, звичайно, можете це робити у власному програмному забезпеченні, яке ви та ніхто більше не використовуєте, якщо знаєте, що ви ніколи не будете використовувати будь-яку мову, крім англійської. В іншому випадку не йти.

Злом був аргументований "добре" десь 30-35 років тому, коли комп'ютери насправді не дуже багато займалися, окрім англійської в ASCII, і, можливо, однією або двома основними європейськими мовами. Але ... вже не так.

Зловмисник працює, тому що верхньо-нижній регістр у США та Латині знаходиться точно 0x20один від одного і відображається в одному порядку, що становить лише один біт різниці. Що, власне, цей трохи хак, перемикає.

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

У реалізатори були, однак, НЕ тактовні досить , щоб тримати це відбувається. Наприклад, якщо ви застосуєте свій хакер на деяких східноєвропейських мовах тощо (я б не знав про кирилицю), ви отримаєте неприємний сюрприз. Усі ці символи "капелюшків" - це приклади цього, малі та великі регістри - один на одного. Таким чином, злом не працює належним чином.

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

Навіть не думайте про те, що цей хак зробить для таких матеріалів, як тайська чи китайська (це просто дасть вам повну дурницю).

Збереження декількох сотень циклів процесора, можливо, було дуже вартим 30 років тому, але в наш час насправді немає приводу для правильного перетворення рядка. Існують бібліотечні функції для виконання цього нетривіального завдання.
Час, необхідний для належного перетворення декількох десятків кілобайт тексту, зараз незначний.


2
Я повністю згоден - хоча кожен програміст є гарною ідеєю знати, чому це працює - може навіть зробити гарне запитання про інтерв'ю .. Що це робить і коли його слід використовувати :)
Білл К

33

Це працює, тому що, як це відбувається, різниця між 'a' і A 'в ASCII і похідних кодуваннях становить 32, а 32 - це також значення шостого біта. Перегортання 6-го біта за допомогою ексклюзивного АБО, таким чином, перетворюється між верхнім і нижнім.


22

Швидше за все, ваша реалізація набору символів буде ASCII. Якщо ми подивимось на таблицю:

введіть тут опис зображення

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

Зауважте, що він працює з усіма символами, а не лише літерами. Він перемикає персонаж із відповідним символом, де шостий біт відрізняється, внаслідок чого пара символів перемикається вперед і назад між. Для літер відповідні великі та малі символи утворюють таку пару. A NULзміниться Spaceі навпаки, і@ перемикання з лапками. В основному будь-який символ у першому стовпчику цієї діаграми перемикається з символом на один стовпець, а той самий стосується третього та четвертого стовпців.

Я б не використовував цей злом, оскільки немає гарантії, що він буде працювати в будь-якій системі. Просто використовуйте замість цього тупер і толоуер , а також запити, такі як isupper .


2
Ну, це не працює для всіх літер, які мають різницю 32. В іншому випадку воно буде працювати між "@" і ""!
Матьє Брюхер

2
@MatthieuBrucher Це працює, 32 ^ 32це 0, а не 64
NathanOliver

5
"@" і "" не є "літерами". Тільки [a-z]і [A-Z]є "букви". Решта - збіги, які дотримуються того самого правила. Якби хтось попросив вас "верхній регістр]", що б це було? все одно це буде "]" - "}" не "верхній регістр" "".
Freedomn-m

4
@MatthieuBrucher: Ще один спосіб зробити це - те, що алфавітні рядки з малого та верхнього регістру не перетинають %32межі "вирівнювання" в системі кодування ASCII. Ось чому біт 0x20є єдиною різницею між верхніми / малими версіями тієї самої літери. Якщо це не так, вам потрібно буде додати або відняти 0x20, а не просто переключити, а для деяких літер було б перенести інші більш високі біти. (І цю ж операцію не вдалося переключити, і перевірити алфавітні символи в першу чергу буде складніше, тому що ви не |= 0x20зможете змусити вивільнити.)
Пітер Кордес

2
+1, щоб нагадати мені про всі ці відвідування asciitable.com, щоб дивитись на цю точну графіку (і розширену версію ASCII !!) за останні, я не знаю, 15 чи 20 років?
AC

15

Тут є багато хороших відповідей, які описують, як це працює, але чому це працює саме так, - це покращити продуктивність. Побітові операції швидше, ніж більшість інших операцій в процесорі. Ви можете швидко здійснити порівняння з нечутливим випадком, просто не переглянувши біт, який визначає регістр, або змінити регістр на верхній / нижній, просто перевернувши біт (ті хлопці, які розробили таблицю ASCII, були досить розумні).

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

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

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

ПРИМІТКА. Я б рекомендував використовувати стандартні бібліотеки для роботи з рядками з кількох причин (читабельність, правильність, портативність тощо). Використовуйте біт фліппінг, лише якщо ви виміряли продуктивність, і це ваше вузьке місце.


14

Ось як працює ASCII, ось і все.

Але, використовуючи це, ви відмовляєтесь від мобільності, оскільки C ++ не наполягає на ASCII як кодуванні.

Ось чому функції std::toupperта std::tolowerреалізовані в стандартній бібліотеці C ++ - ви повинні використовувати їх замість них.


6
Однак є протоколи, які вимагають використання ASCII, наприклад DNS. Насправді, "фокус 0x20" використовується деякими серверами DNS для вставки додаткової ентропії в запит DNS як механізм протидії підробці. DNS не чутливий до регістру, але також повинен зберігати регістр, тому, якщо надіслати запит із випадковим регістром і повернути той самий випадок, це хороший показник того, що відповідь не була підроблена третьою стороною.
Альнітак

Варто зазначити, що багато кодувань все ще мають однакове представлення для стандартних (не розширених) символів ASCII. Але все ж, якщо ви дійсно переживаєте за різні кодування, вам слід використовувати належні функції.
Людина капітана

5
@CaptainMan: Абсолютно. UTF-8 - це суцільна краса. Сподіваємось, він потрапляє у стандарт C ++, якщо IEEE754 має плаваючу точку.
Вірсавія

11

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

Модифікатор управління на вашій клавіатурі в основному очищає три верхні біти будь-якого символу, який ви вводите, залишаючи нижню п'ятірку та відображаючи її до діапазону 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.( знайдено тут ).


1
Не вдалося реалізувати перемикання зрушень для більшої частини ASCII w / foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20, хоча це лише ASCII і тому нерозумно з причин, зазначених в інших відповідях. Можливо, це також може бути покращене програмування без віток.
Ірідайн

1
Ах, foo ^= 0x20 >> !(foo & 0x40)було б простіше. Також хороший приклад того, чому короткий код часто вважають нечитаним ^ _ ^.
Ірідаїн

8

Xoring з 32 (00100000 у двійкових) встановлює або скидає шостий біт (справа). Це суворо еквівалентно додаванню чи відніманню 32.


2
Ще один спосіб сказати це - XOR - це доповнення без перенесення.
Пітер Кордес

7

Нижній і верхній регістри алфавіту не перетинають %32межі "вирівнювання" в системі кодування ASCII.

Ось чому біт 0x20є єдиною різницею між верхніми / малими версіями тієї самої літери.

Якщо це не так, вам потрібно буде додати або відняти 0x20, а не просто переключити, а для деяких літер було б перенести інші більш високі біти. (І не було б жодної операції, яка могла б перемикати, і перевірити алфавітні символи в першу чергу буде складніше, тому що ви не змогли | = 0x20 змусити lcase.)


Пов’язані лише підказки ASCII: ви можете перевірити наявність алфавітного символу ASCII , висунувши малі літери c |= 0x20та перевіривши, чи немає (без підпису) c - 'a' <= ('z'-'a'). Отже, лише 3 операції: OR + 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 за допомогою цієї перевірки.)

А також Як отримати доступ до масиву char та змінити малі літери на великі регістри, і навпаки (C із вбудованими SIMD-сигналами та скалярним шрифтом x86 asm-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для кожного персонажа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.