web-dev-qa-db-ja.com

^ = 32の背後にある考え方は何ですか?小文字を大文字に、またはその逆に変換しますか?

私はコードフォースに関するいくつかの問題を解決していました。通常、文字が英語の大文字か小文字かを最初に確認してから、32を使用して、対応する文字に変換します。しかし、誰かが_^= 32同じことをする。ここにあります:

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

私はこれの説明を探しましたが、わかりませんでした。なぜこれが機能するのでしょうか?

146
Devon

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

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

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

149
Hanjoung Lee

これは、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からカウント)。

117
YSC

これは(スマートに見えますが)本当に本当に馬鹿げたハックだと言わせてください。 2019年に誰かがあなたにこれを勧めたら、彼を叩いてください。彼をできるだけ強く叩いてください。
もちろん、英語以外の言語を使用しないことがわかっている場合は、自分と他の誰も使用していない独自のソフトウェアで実行できます。さもなければ、行かない。

このハッキングは30〜35年前に議論された「OK」でした。当時、コンピューターはASCIIの英語以外はほとんど何もしなかったため、多分 1つまたは2つのヨーロッパの主要言語です。しかし...もはやそうではありません。

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

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

しかし、実装者はこれを続けるのに十分な not 思いやりがありました。たとえば、一部の東ヨーロッパ言語などでハックを適用すると(キリル文字については知りませんが)、意外な驚きがあります。これらの「ハッチェット」文字はすべてその例であり、小文字と大文字は1つに分かれています。したがって、ハックは not が適切に機能します。

例えば、いくつかの文字は、単に小文字から大文字にまったく変換されない(異なるシーケンスに置き換えられる)か、フォームを変更する(異なるコードポイントが必要)場合があります。

このハックがタイ語や中国語のようなものに対して何をするかさえ考えないでください(それはあなたに完全なナンセンスを与えるだけです)。

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

35
Damon

それが機能するのは、たまたま、ASCIIと派生エンコーディングの 'a'とA 'の差が32であり、32も6番目のビットの値だからです。排他的ORは、このように上位と下位を変換します。

33
Jack Aidley

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

enter image description here

小文字の値と大文字の値の間には、正確に32の違いがあることがわかります。したがって、^= 32(6番目の最下位ビットの切り替えに相当)を実行すると、小文字と大文字の間で変更されます。

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

ただし、どのシステムでも動作することを保証するものではないため、このハックは使用しません。代わりに toupper および tolower を使用し、 isupper などのクエリを使用します。

22
Blaze

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

明らかに、これは1960年(ASCIIで最初に作業が開始されたとき)に比べて、プロセッサとユニコードが高速であるため、今日の取引ほど大きくはありませんが、大きな違いを生む可能性のある低コストプロセッサがまだいくつかありますASCII文字のみを保証できる限り。

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

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

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

15
Brian

ASCIIの仕組み、それがすべてです。

しかし、これを悪用する場合、C++はASCIIをエンコードとして要求しないため、portability)を放棄しています。

これが関数std::toupperおよびstd::tolowerはC++標準ライブラリに実装されています-代わりに使用する必要があります。

14
Bathsheba

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 キーボードキーは、多くの(またはおそらく ctrl)ロジック- shift おそらく数個のゲートのみが必要です。おそらく、ワイヤプロトコルを他の文字エンコーディングと同じくらい格納することは理にかなっています(ソフトウェア変換は不要です)。

リンクされた記事alsoは、And control H does a single character and is an old^H^H^H^H^H classic joke.ここにあります )。

11
Iiridayn

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

8
Yves Daoust

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

これが、ビット_0x20_が同じ文字の大文字/小文字バージョンの唯一の違いである理由です。

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


関連するASCIIのみのトリック:アルファベットのASCII文字を確認するには、_c |= 0x20_で小文字を強制し、(符号なし)c - 'a' <= ('z'-'a')。したがって、たった3つの操作:OR + SUB +定数25に対するCMP。もちろん、コンパイラーは_(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++の文字列を大文字に変換する (ASCIIのみのSIMD文字列toupper、そのチェックを使用してXORのオペランドをマスクする)も参照してください。

また、 char配列にアクセスし、小文字を大文字に、またはその逆に変更する方法 (SIMD組み込み関数を使用するC、およびアルファベットのASCII文字のスカラーx86 asm case-flip 、その他は変更されません。)


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

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


ただし、場合によっては、UTF-8の代わりにASCIIを想定することができます。 _LANG=C_などではなく、_en_CA.UTF-8_(POSIXロケール)を使用するUnixユーティリティ。

しかし、安全であることを確認できれば、ループ内でtoupper()(5xなど)を呼び出すよりもはるかに高速にtoupper中程度の長さの文字列を使用でき、 最後にBoost 1.58でテスト 、はるかにmuchすべての文字に対して愚かな_dynamic_cast_を実行するboost::to_upper_copy<char*, std::string>()よりも高速です。

7
Peter Cordes