^ = 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、それは実際には「機能しません」。これはこの特定の文字セットで機能しますが、使用しないケースtouppertolowerケースを切り替える他のセットがあります。
NathanOliver

7
いつかオンラインコンテストの「アイデア」は、真剣なレビューに合格しないような難読化された方法でコードを書くことです;)
idclev 463035818

21
^ =はXORを使用して値を変換しています。大文字のASCII文字は対応するビットにゼロがあり、小文字は1です。とはいえ、しないでください!適切な文字(Unicode)ルーチンを使用して、小文字と大文字の間で変換します。ASCIIだけの時代はもう終わりました。
Hans-Martin Mosner

14
一部の文字セットでしか機能しないというだけではありません。すべての世界がUTF-8(少なくともユートピアの素晴らしい目標かもしれません)であると仮定しても、それはから26文字Aまでしか機能しませんZ。英語だけに関心があれば問題ありませんが( "naïve"、 "café"などの単語、発音区別符号付きの名前は使用しないでください)、世界は英語だけではありません。
ilkkachu

回答:


149

バイナリのASCIIコード表を見てみましょう。

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

また、32は0100000小文字と大文字の唯一の違いです。したがって、そのビットを切り替えると、文字の大文字と小文字が切り替わります。


49
ASCIIだけのために*「ケースを切り替えます」
ダックMooing

39
ASCIIのA-Za-zに対してのみ@Mooing。「[」の小文字は「{」ではありません
dbkk

21
@dbkk {はより短い[ので、「より低い」ケースです。番号?
わかりました

25
雑学:7ビットの領域では、ドイツ語のコンピューターでは[] {|}がÄÖÜäöüに再マッピングされました。なぜなら、これらの文字より多くのウムラウトが必要だったため、{(ä)は実際は小文字の[(Ä)でした。
Guntram Blohmは

14
@GuntramBlohmさらにトリビアちらほらなぜ、これはあるIRCサーバが考慮する foobar[]foobar{}同じニックネームであることをニックネームはケースですと、小文字を区別しない、とIRCはスカンジナビアで、その起源を持っている:)
ZeroKnight

117

これは、ASCII値が本当に賢い人々によって選択されているという事実を利用しています。

foo ^= 32;

これにより、(ASCIIソートの大文字のフラグ)の6番目に低いビット1反転し、foo 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 紙テープで使用されました。削除文字が1111111としてエンコードされているのはそのためです。したがって、テープの列にあるすべての穴を空けることによって、任意の文字を「削除済み」としてマークできます。
dan04

23
@Bathshebaは、パンチカードを使用したことがない人なので、EBCDICがインテリジェントに設計されたという考えに頭を抱えるのは非常に困難です。
ロードファークアド

9
@LordFarquaad IMHOパンチカードに文字を書き込む方法のWikipediaの図は、EBCDICがこのエンコーディングに対していくらか(ただし、全体ではなく、/対S)を理解する方法を明らかに示しています。en.wikipedia.org/wiki/EBCDIC#/media/...
Peteris

11
@ dan04「MASSEの小文字の形式は何ですか?」に注意してください。知らない人のために、大文字の形式がMASSEであるドイツ語には2つの単語があります。1つは「マッセ」、もう1つは「マッセ」です。適切なtolowerドイツ語は、辞書を必要としないだけでなく、意味を解析できる必要があります。
Martin Bonnerがモニカをサポートする

35

これは-スマートなように見えますが-本当に、本当に愚かなハックです。2019年に誰かがこれをあなたにすすめた場合は、ヒットしてください。できる限り彼を殴る。
もちろん、あなたは自分のソフトウェアでそれを行うことができます。あなたがとにかく英語以外の言語を決して使用しないことを知っているなら、あなたや他の誰も使用しません。それ以外の場合は、行きません。

ハックは、30〜35年前にコンピュータが実際にはASCIIで英語以外の多くのことをしなかったとき、「大丈夫」であると論じられました。 1つまたは2つの主要なヨーロッパ言語でしたが。しかし...もうそうではありません。

US-Latinの大文字と小文字が正確に0x20離れており、同じ順序で表示されるため、ハッキングが機能します。これは、わずか1ビットの違いです。実際、このビットハックはトグルします。

現在、西ヨーロッパのコードページを作成している人々、そして後にユニコードコンソーシアムは、たとえばドイツ語のウムラウトやフランス語をアクセントにした母音のためにこのスキームを維持するのに十分賢くなりました。(2017年に誰かがUnicodeコンソーシアムを納得させるまで、そして大規模なフェイクニュースの印刷雑誌がそれについて書いて、実際にDudenを説得するまで-それについてのコメントはありません)汎用(SSに変換)さえ存在しない ßにはそうではありません。今で万能として存在ますが、2つは0x1DBF離れた位置にあり、ではありません0x20

しかし、実装者たちはこれを続けるのに十分な配慮がありませんでした。たとえば、東ヨーロッパの言語などでキックを適用した場合(キリル文字については知りません)、意外な驚きを感じるでしょう。それらすべての「ハチェット」文字はその例であり、小文字と大文字は1つずつ離れています。したがって、ハックはそこで正しく機能しませ

たとえば、一部の文字は単に小文字から大文字に変換されないだけでなく(別のシーケンスに置き換えられます)、形式が変わる可能性があります(別のコードポイントが必要)。

このハックがタイ語や中国語などに何をするかについてさえ考えないでください(完全にナンセンスになるだけです)。

数百のCPUサイクルを節約することは、30年前には非常に価値があったかもしれませんが、今日では、文字列を適切に変換するための言い訳は本当にありません。この重要なタスクを実行するためのライブラリ関数があります。
数十キロバイトのテキストを適切に変換するのにかかる時間は、今日ではごくわずかです。


2
私は完全に同意する-それが動作する理由すべてのプログラマが知っているのは良いアイデアですが-でも良い面接の質問をするかもしれない...何をして、これはやるんし、それを使用する必要があるときに:)
ビル・K

33

これが機能するのは、たまたま、ASCIIの「a」と「A」および派生エンコーディングの違いが32であり、32も6番目のビットの値だからです。したがって、6番目のビットを排他的ORで反転すると、上位と下位の間で変換されます。


22

ほとんどの場合、文字セットの実装はASCIIです。テーブルを見ると:

ここに画像の説明を入力してください

32小文字と大文字の数値には正確な違いがあることがわかります。したがって、もし^= 32(6番目の最下位ビットをトグルすることと同じ)、小文字と大文字が切り替わります。

文字だけでなく、すべての記号で機能することに注意してください。これは、6番目のビットが異なるそれぞれの文字で文字を切り替え、その結果、1組の文字が前後に切り替えられます。文字については、それぞれの大文字/小文字がそのようなペアを形成します。A NULはに変更されSpace、その逆になります。@はとし、バックティックでトグルします。基本的に、このチャートの最初の列にある文字は、1列上の文字に切り替わり、同じことが3番目と4番目の列にも当てはまります。

ただし、どのシステムでも機能することを保証するものではないため、このハックは使用しません。代わりにtouppertolowerを使用し、isupperなどのクエリを使用してください。


2
まあ、それは32の違いがあるすべての文字に対しては機能しません。それ以外の場合は、 '@'と ''の間で機能します。
Matthieu Brucher

2
@MatthieuBrucherこれは機能しており、32 ^ 3264ではなく0です
NathanOliver

5
'@'と ''は「文字」ではありません。のみ[a-z][A-Z]「手紙」です。残りは同じルールに従う偶然です。誰かがあなたに「大文字]」と尋ねた場合、それは何でしょうか?それでも「]」のままです-「}」は「]」の「大文字」ではありません。
freedomn-m

4
@MatthieuBrucher:そのポイントを作成する別の方法%32は、ASCIIコーディングシステムで、小文字と大文字のアルファベット範囲が「配置」境界を超えないことです。 このため0x20、同じ文字の大文字と小文字のバージョンの違いはビットだけです。これが当てはまらない場合は、0x20トグルだけでなく、を加算または減算する必要があります。一部の文字では、他の上位ビットを反転するためのキャリーアウトがあります。(そして、同じ操作はトグルできません|= 0x20でした。そもそもlcaseを強制することができなかったため、最初にアルファベット文字をチェックすることは困難です。)
Peter Cordes

2
15年または20年前の最後の正確なグラフィック(および拡張ASCIIバージョン!!)を見つめるためにasciitable.comにアクセスしたことを思い出させるための+1?
AC

15

これがどのように機能するかを説明する良い回答がたくさんありますが、なぜこのように機能するのかはパフォーマンスを向上させるためです。ビット単位の演算は、プロセッサ内の他のほとんどの演算よりも高速です。単純に大文字と小文字を区別しない比較を行うには、大文字と小文字を決定するビットを単に見ないか、ビットを反転するだけで大​​文字と小文字を大文字/小文字に変更します(ASCIIテーブルを設計した人たちはかなり賢明でした)。

明らかに、これは1960年(最初にASCIIで作業が始まったとき)に戻ったときほど高速ではないプロセッサーとUnicodeのため、それほど大きな問題ではありませんが、大きな違いをもたらす可能性のある低コストのプロセッサーがまだいくつかありますASCII文字のみを保証できる限り。

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

単純な低コストプロセッサでは、通常、ビット単位の演算は除算よりも大幅に高速で、乗算よりも数倍高速で、加算よりも大幅に高速な場合があります。

注:いくつかの理由(読みやすさ、正確さ、移植性など)のため、標準ライブラリを使用して文字列を操作することをお勧めします。パフォーマンスを測定し、これがボトルネックである場合にのみ、ビットフリッピングを使用してください。


14

ASCIIが機能する方法はこれだけです。

しかし、これを利用すると、移植性をあきらめます C ++はエンコードとしてASCIIを要求しない。

これが関数std::toupperstd::tolowerC ++標準ライブラリに実装されている理由です-代わりにそれらを使用する必要があります。


6
ただし、DNSなど、ASCIIを使用する必要があるプロトコルもあります。実際、「0x20トリック」は、一部のDNSサーバーで、スプーフィング防止メカニズムとしてDNSクエリに追加のエントロピーを挿入するために使用されます。DNSは大文字と小文字を区別しませんが、大文字と小文字を保持することも想定されているため、ランダムな大文字と小文字を使用してクエリを送信し、同じ大文字と小文字を返せば、応答が第三者によって偽装されていないことを示しています。
アルニタク

多くのエンコーディングが標準の(拡張されていない)ASCII文字に対して同じ表現を持っていることは言及する価値があります。しかし、それでも、さまざまなエンコーディングが本当に心配な場合は、適切な関数を使用する必要があります。
キャプテンマン

5
@CaptainMan:もちろんです。UTF-8はまったくの美しさです。うまくいけば、IEEE754が浮動小数点に対して持っている限り、C ++標準に「吸収」されます。
バトシェバ

11

http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_asciiの2番目の表と、以下に再現した以下のメモを参照してください

キーボードのControl修飾子は、基本的に、入力した文字の上位3ビットをクリアし、下位5ビットを残して0〜31の範囲にマッピングします。したがって、たとえば、Ctrl-SPACE、Ctrl- @、およびCtrl-`はすべて同じ意味、NULを意味します。

非常に古いキーボードは、キーに応じて32ビットまたは16ビットを切り替えるだけでShiftキーを押していました。これが、ASCIIでの小文字と大文字の関係が非常に規則的であり、数字と記号、および記号のペアの関係が目を細めるとある程度規則的である理由です。ASR-33はすべて大文字のターミナルでしたが、16ビットをシフトすることで、キーがなかった句読文字を生成することもできます。したがって、たとえば、Shift-K(0x4B)は[(0x5B)

ASCIIは、shiftおよびctrlキーボードのキーを、多くの(またはおそらくforのctrl)ロジックなしで実装できるように設計されました-shiftおそらく数個のゲートしか必要としません。おそらく、ワイヤプロトコルを他の文字エンコーディングと同じくらい格納することは理にかなっています(ソフトウェアの変換は必要ありません)。

リンクされた記事は、(ここにあります)など、多くの奇妙なハッカー規約について説明しています。And control H does a single character and is an old^H^H^H^H^H classic joke.


1
foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20これはASCIIだけなので、他の回答に記載されている理由から賢明ではありませんが、ASCIIのw /のシフトトグルを実装できます。おそらくブランチフリープログラミングで改善することもできます。
Iiridayn

1
ああ、foo ^= 0x20 >> !(foo & 0x40)もっと簡単でしょう。また、簡潔なコードが読みにくいと見なされることがよくある良い例です^ _ ^。
Iiridayn

8

32(バイナリで00100000)をXoringすると、(右から)6番目のビットが設定またはリセットされます。これは、32の加算または減算と厳密に同等です。


2
これを別の言い方をすると、XORはキャリーの追加なしです。
Peter Cordes

7

小文字と大文字のアルファベット範囲は%32、ASCIIコーディングシステムの「配置」境界を超えません。

このため0x20、同じ文字の大文字と小文字のバージョンの違いはビットだけです。

これが当てはまらない場合は、0x20トグルするだけでなく、を加算または減算する必要があります。一部の文字では、他の上位ビットを反転するためのキャリーアウトがあります。(また、トグルできる単一の操作はありません。また、lcaseを強制的に| = 0x20できないため、最初にアルファベット文字をチェックすることは困難です。)


関連するASCIIのみのトリック:小文字で強制的に小文字のASCII文字c |= 0x20をチェックしてから、(符号なし)かどうかをチェックできc - 'a' <= ('z'-'a')ます。つまり、3つの演算:定数25に対するOR + SUB + CMPです。もちろん、コンパイラーは(c>='a' && c<='z') このようにasmに最適化する方法を知っているので、多くてもc|=0x20自分で行う必要があります。必要なキャストをすべて自分で行うのは、特にデフォルトの整数の昇格をsignedに回避するのはかなり不便です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配列にアクセスして小文字を大文字に、またはその逆に変更する方法 (SIMD組み込み関数を使用したC、およびアルファベットのASCII文字用のスカラーx86 asmケースフリップ、その他は変更せずに残します)。


これらのトリックは、SIMD(SSE2やNEONなど)でテキスト処理を手動で最適化する場合にのみ役立ちます。 char、ベクター内のどのsにも上位ビットが設定されて。(したがって、どのバイトも、単一の文字のマルチバイトUTF-8エンコーディングの一部ではありません。大文字と小文字の逆が異なる場合があります)。見つかった場合は、この16バイトのチャンクまたは文字列の残りのスカラーにフォールバックできます。

一部のロケールでは、ASCII範囲の一部の文字toupper()またはtolower()その一部でその範囲外の文字が生成されます。特にトルコ語では、I Iıおよびİ↔iです。 これらのロケールでは、より高度なチェックが必要になるか、この最適化をまったく使用しないようにする必要があります。


しかし、場合によっては、UTF-8の代わりにASCIIを使用することが許可されていLANG=Cますen_CA.UTF-8

あなたはそれが安全で確認することができる場合でも、あなたはできるtoupperミディアムの長さの文字列をずっと速く呼び出すよりtoupper()(5倍のような)ループの中で、そして最後に私はブースト1.58でテストずっと、ずっと速くよりもboost::to_upper_copy<char*, std::string>()愚かをしているdynamic_castすべての文字のために。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.