一般的な実装
大文字と小文字を変換するコードを書くとするなら、以下のようにするのが一般的だと思われます。
char toUpper(char c) {
return ('a' <= c && c <= 'z') ? ('A' + c - 'a') : c;
}
char toLower(char c) {
return ('A' <= c && c <= 'Z') ? ('a' + c - 'A') : c;
}
修正前のコード
(2024/03/07修正) 「diff
を用いるのはややこしいのではないか」というご意見をいただき、以下のコードから修正しました。
char toUpper(char c) {
auto diff = c - 'a';
return (0 <= diff && diff <= ('z' - 'a')) ? ('A' + diff) : c;
}
char toLower(char c) {
auto diff = c - 'A';
return (0 <= diff && diff <= ('Z' - 'A')) ? ('a' + diff) : c;
}
ビット演算を用いた変換
手元の環境で実行してみればわかりますが、これで本当に動きます。
ロマンあふれるコードですね。
using byte = unsigned char;
char toUpper(char c) {
char upper = c & ~0x20;
return ('A' <= upper && upper <= 'Z') ? upper : c;
}
char toLower(char c) {
char lower = c | 0x20;
return ('a' <= lower && lower <= 'z') ? lower : c;
}
このコードについての説明は以下に記載します。
修正前のコード
(2024/03/07修正) return
部分の比較演算を、以下から修正しました。
using byte = unsigned char;
char toUpper(char c) {
char upper = c & ~0x20;
return ((byte)(upper - 'A') <= (byte)('Z' - 'A')) ? upper : c;
}
char toLower(char c) {
char lower = c | 0x20;
return ((byte)(lower - 'a') <= (byte)('z' - 'a')) ? lower : c;
}
簡単な解説
A~Z と a~z のASCIIコードをビットに直して見比べてみるとよく分かります。
文字 | 大文字 | 小文字 |
---|---|---|
A/a | 0100_0001 | 0110_0001 |
B/b | 0100_0010 | 0110_0010 |
C/c | 0100_0011 | 0110_0011 |
... | ... | ... |
Y/y | 0101_1001 | 0111_1001 |
Z/z | 0101_1010 | 0111_1010 |
大文字と小文字は, 0x20
でビットが立っている位置以外のビットは一致しているので、これを用いることで、大文字と小文字の変換を上のように記述することができます。
補足
(2024/03/07追記) このセクションの内容は本記事の主題から大きく逸れていたのと、C++ではこの方法には意味がないことが判明したため、修正しました。
修正前の内容
上のコードで、returnの部分にかかれているこの条件式について説明します。
(unsigned char)(lower - 'a') <= (unsigned char)('z' - 'a')
これは以下の条件式と同等です。
0 <= (lower - 'a') && (lower - 'a') <= ('z' - 'a')
詳しい説明は、以下の記事が分かりやすいです。
おわりに
ビット演算による実装は実際に様々なところで用いられており、.NET API の実装 でも見られます。
実際には元から用意されている関数を用いることが多いと思われますし、ここで紹介した方法ではàとÀの相互変換などには対応できません。
以上、小ネタでした。