Apa ide di balik ^ = 32, yang mengubah huruf kecil menjadi huruf besar dan sebaliknya?

146

Saya sedang memecahkan beberapa masalah pada codeforces. Biasanya saya pertama kali memeriksa apakah karakternya huruf Inggris atas atau bawah kemudian kurangi atau tambahkan 32untuk mengubahnya menjadi huruf yang sesuai. Tetapi saya menemukan seseorang melakukan ^= 32hal yang sama. Ini dia:

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

Saya telah mencari penjelasan untuk ini dan tidak mengetahuinya. Jadi mengapa ini berhasil?

Devon
sumber
5
en.wikipedia.org/wiki/File:USASCII_code_chart.png Tip: Anda dapat mengkonversi @menjadi `dengan menggunakan ^ 32.
KamilCuk
112
FWIW, itu tidak benar-benar "berfungsi". Ini berfungsi untuk set karakter khusus ini tetapi ada set lain di mana tidak akan Anda harus menggunakan toupperdan toloweruntuk beralih kasus.
NathanOliver
7
kadang-kadang dengan kontes online "ide" adalah menulis kode sedemikian rupa sehingga tidak akan pernah melewati tinjauan serius;)
idclev 463035818
21
^ = mentransformasikan nilai menggunakan XOR. Huruf ASCII huruf besar memiliki nol dalam bit yang sesuai, sedangkan huruf kecil memiliki satu. Yang mengatakan, tolong jangan! Gunakan rutinitas karakter (unicode) yang tepat untuk mengkonversi antara huruf kecil dan huruf besar. Era ASCII sudah lama berlalu.
Hans-Martin Mosner
14
Ini tidak hanya berfungsi dengan beberapa set karakter. Bahkan jika kita menganggap semua dunia adalah UTF-8 (yang kekuatan setidaknya menjadi tujuan utopis bagus), juga hanya bekerja dengan 26 huruf Auntuk Z. Tidak apa-apa asalkan Anda hanya peduli dengan bahasa Inggris (dan jangan menggunakan ejaan "naif", kata-kata seperti "kafe", atau nama-nama dengan diakritik ...), tetapi dunia bukan hanya bahasa Inggris.
ilkkachu

Jawaban:

149

Mari kita lihat tabel kode ASCII dalam biner.

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

Dan 32 adalah 0100000satu-satunya perbedaan antara huruf kecil dan huruf besar. Jadi, beralih sedikit itu mengubah huruf.

Hanjoung Lee
sumber
49
"toggles the case" * hanya untuk ASCII
Mooing Duck
39
@Mooing hanya untuk A-Za-z di ASCII. Huruf kecil "[" bukan "{".
dbkk
21
@ dbkk {lebih pendek dari [, jadi ini adalah huruf "lebih rendah". Tidak? Ok, saya akan menunjukkan diri saya: D
Peter Badida
25
Berita gembira Trivia: Di area 7 bit, komputer Jerman telah [] {|} memetakan kembali ke ÄÖÜäöü karena kami membutuhkan Umlaut lebih dari karakter itu, jadi dalam konteks itu, {(ä) sebenarnya adalah huruf kecil [(Ä).
Guntram Blohm mendukung Monica
14
@GuntramBlohm Trivia tidbit lebih lanjut, inilah mengapa server IRC mempertimbangkan foobar[] dan foobar{}menjadi nama panggilan yang identik, karena nama panggilan tidak peka terhadap huruf besar-kecil , dan IRC memiliki asal-usulnya di Skandinavia :)
ZeroKnight
117

Ini menggunakan fakta daripada nilai-nilai ASCII telah dipilih oleh orang-orang yang benar-benar pintar.

foo ^= 32;

Ini membalik-6 bit terendah 1 dari foo(bendera huruf besar dari ASCII semacam), mengubah sebuah huruf ASCII untuk kasus yang lebih rendah dan sebaliknya .

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

Contoh

'A' ^ 32

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

Dan dengan properti XOR 'a' ^ 32 == 'A',.

Memperhatikan

C ++ tidak diperlukan untuk menggunakan ASCII untuk mewakili karakter. Varian lain adalah EBCDIC . Trik ini hanya berfungsi pada platform ASCII. Solusi yang lebih portabel adalah menggunakan std::tolowerdan std::toupper, dengan bonus yang ditawarkan menjadi sadar-lokal (itu tidak secara otomatis menyelesaikan semua masalah Anda, lihat komentar):

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) Sebagaimana 32 adalah 1 << 5(2 pangkat 5), ia membalik bit ke-6 (dihitung dari 1).

YSC
sumber
16
EBCDIC dipilih oleh beberapa orang yang sangat pintar juga: bekerja dengan sangat baik pada kartu berlubang, lih. ASCII yang berantakan. Tapi ini jawaban yang bagus, +1.
Batsyeba
65
Aku tidak tahu tentang kartu punch, tetapi ASCII itu digunakan pada pita kertas. Itulah sebabnya karakter Delete dikodekan sebagai 1111111: Jadi Anda dapat menandai karakter apa saja sebagai "dihapus" dengan meninju semua lubang di kolomnya pada kaset.
dan04
23
@Bathsheba sebagai seseorang yang belum menggunakan kartu punch, sangat sulit untuk membungkus kepala saya dengan gagasan bahwa EBCDIC dirancang dengan cerdas.
Lord Farquaad
9
@LordFarquaad IMHO gambar Wikipedia tentang bagaimana huruf-huruf ditulis pada punchcard adalah ilustrasi yang jelas tentang bagaimana EBCDIC memang membuat beberapa (tetapi tidak total, lihat / vs S) rasa untuk pengkodean ini. en.wikipedia.org/wiki/EBCDIC#/media/…
Peteris
11
@ dan04 Catatan untuk menyebutkan "apa bentuk huruf kecil dari 'MASSE'?". Bagi mereka yang tidak tahu, ada dua kata dalam bahasa Jerman yang bentuk huruf besar adalah MASSE; satu adalah "Masse" dan yang lainnya adalah "Maße". Proper tolowerdalam bahasa Jerman tidak hanya membutuhkan kamus, tetapi juga harus mampu menguraikan artinya.
Martin Bonner mendukung Monica
35

Izinkan saya untuk mengatakan bahwa ini - walaupun tampaknya cerdas - peretasan yang benar-benar bodoh. Jika seseorang merekomendasikan ini kepada Anda pada tahun 2019, pukul dia. Pukul dia sekeras yang Anda bisa.
Anda dapat, tentu saja, melakukannya dalam perangkat lunak Anda sendiri yang Anda dan orang lain gunakan jika Anda tahu bahwa Anda tidak akan pernah menggunakan bahasa apa pun selain bahasa Inggris. Kalau tidak, jangan pergi.

Peretasan itu bisa dibilang "OK" sekitar 30-35 tahun yang lalu ketika komputer tidak benar-benar melakukan banyak hal selain bahasa Inggris di ASCII, dan mungkin satu atau dua bahasa utama Eropa. Tapi ... tidak lagi begitu.

Peretasan ini bekerja karena huruf AS-Latin bagian atas dan bawah persis 0x20terpisah satu sama lain dan muncul dalam urutan yang sama, yang hanya memiliki sedikit perbedaan. Yang mana, pada kenyataannya, ini sedikit hack, matikan.

Sekarang, orang-orang yang membuat halaman kode untuk Eropa Barat, dan kemudian konsorsium Unicode, cukup pintar untuk menjaga skema ini misalnya Umlaut Jerman dan Vokal beraksen Prancis. Tidak demikian untuk ß yang (sampai seseorang meyakinkan konsorsium Unicode pada 2017, dan sebuah majalah cetak Fake News yang besar menulis tentang hal itu, benar-benar meyakinkan Duden - tidak ada komentar tentang itu) bahkan tidak ada sebagai versal (berubah menjadi SS) . Sekarang memang ada sebagai versal, tetapi keduanya adalah 0x1DBFposisi yang terpisah, bukan 0x20.

Namun, implementornya tidak cukup perhatian untuk mempertahankan hal ini. Misalnya, jika Anda menerapkan retas dalam beberapa bahasa Eropa Timur atau sejenisnya (saya tidak akan tahu tentang Cyrillic), Anda akan mendapatkan kejutan yang tidak menyenangkan. Semua karakter "hatchet" adalah contoh dari itu, huruf kecil dan huruf besar adalah satu terpisah. Peretasan tidak bekerja dengan baik di sana.

Ada banyak lagi yang perlu dipertimbangkan, misalnya, beberapa karakter tidak hanya berubah dari huruf kecil ke huruf besar sama sekali (mereka diganti dengan urutan yang berbeda), atau mereka dapat mengubah bentuk (memerlukan titik kode yang berbeda).

Jangan pernah berpikir tentang apa yang akan dilakukan peretasan ini untuk hal-hal seperti Thailand atau Cina (itu hanya akan memberi Anda omong kosong).

Menyimpan beberapa ratus siklus CPU mungkin sangat bermanfaat 30 tahun yang lalu, tetapi saat ini, sebenarnya tidak ada alasan untuk mengkonversi string dengan benar. Ada fungsi perpustakaan untuk melakukan tugas non-sepele ini.
Waktu yang dibutuhkan untuk mengonversi beberapa lusin kilobyte teks dengan benar dapat diabaikan saat ini.

Damon
sumber
2
Saya sangat setuju - walaupun ini adalah ide yang bagus untuk setiap programmer untuk mengetahui mengapa ia bekerja - bahkan mungkin membuat pertanyaan wawancara yang baik .. Apa yang dilakukan dan kapan ini harus digunakan :)
Bill K
33

Ini bekerja karena, seperti yang terjadi, perbedaan antara 'a' dan A 'dalam pengkodean ASCII adalah 32, dan 32 juga nilai bit keenam. Membalik bit ke-6 dengan OR eksklusif sehingga mengubah antara atas dan bawah.

Jack Aidley
sumber
22

Kemungkinan besar implementasi set karakter Anda adalah ASCII. Jika kita melihat tabel:

masukkan deskripsi gambar di sini

Kita melihat bahwa ada perbedaan 32antara nilai huruf kecil dan huruf besar. Oleh karena itu, jika kita lakukan ^= 32(yang sama dengan mengganti bit paling signifikan ke-6), itu berubah antara karakter huruf kecil dan huruf besar.

Perhatikan bahwa ini bekerja dengan semua simbol, bukan hanya huruf. Ini beralih karakter dengan karakter masing-masing di mana bit ke-6 berbeda, menghasilkan sepasang karakter yang beralih bolak-balik antara. Untuk huruf, masing-masing karakter huruf besar / kecil membentuk pasangan seperti itu. A NULakan berubah menjadi Spacedan sebaliknya, dan @beralih dengan backtick. Pada dasarnya setiap karakter di kolom pertama pada bagan ini beralih dengan karakter satu kolom di atas, dan hal yang sama berlaku untuk kolom ketiga dan keempat.

Saya tidak akan menggunakan retasan ini, karena tidak ada jaminan bahwa ini akan bekerja pada sistem apa pun. Cukup gunakan toupper dan tolower saja, dan permintaan seperti isupper .

Api
sumber
2
Yah, itu tidak berfungsi untuk semua huruf yang memiliki perbedaan 32. Jika tidak, itu akan bekerja antara '@' dan ''!
Matthieu Brucher
2
@MatthieuBrucher Ini berfungsi, 32 ^ 32adalah 0, bukan 64
NathanOliver
5
'@' dan '' bukan "huruf". Hanya [a-z]dan [A-Z]"huruf". Sisanya adalah kebetulan yang mengikuti aturan yang sama. Jika seseorang meminta Anda untuk "huruf besar]", apakah itu? masih akan menjadi "]" - "}" bukan "huruf besar" dari "]".
freedomn-m
4
@MatthieuBrucher: Cara lain untuk membuat titik itu adalah bahwa rentang alfabet huruf kecil dan huruf besar tidak melewati batas %32"alignment" dalam sistem pengkodean ASCII. Inilah sebabnya mengapa bit 0x20adalah satu-satunya perbedaan antara versi huruf besar / kecil dari huruf yang sama. Jika ini bukan masalahnya, Anda harus menambah atau mengurangi 0x20, bukan hanya beralih, dan untuk beberapa huruf akan ada tugas untuk membalik bit lain yang lebih tinggi. (Dan operasi yang sama tidak dapat diaktifkan, dan memeriksa karakter alfabet di tempat pertama akan lebih sulit karena Anda tidak bisa |= 0x20memaksa lcase.)
Peter Cordes
2
+1 untuk mengingatkan saya tentang semua kunjungan ke asciitable.com untuk menatap grafik yang persis (dan versi ASCII yang diperluas !!) untuk yang terakhir, saya tidak tahu, 15 atau 20 tahun?
AC
15

Banyak jawaban bagus di sini yang menjelaskan cara kerjanya, tetapi mengapa cara ini bekerja adalah untuk meningkatkan kinerja. Pengoperasian bitwise lebih cepat daripada kebanyakan operasi lain dalam suatu prosesor. Anda dapat dengan cepat melakukan perbandingan case yang tidak sensitif dengan tidak melihat bit yang menentukan case atau mengubah case ke atas / bawah hanya dengan membalik bit (orang-orang yang mendesain tabel ASCII cukup pintar).

Jelas, ini bukan masalah besar hari ini seperti pada tahun 1960 (ketika pekerjaan pertama kali dimulai pada ASCII) karena prosesor yang lebih cepat dan Unicode, tetapi masih ada beberapa prosesor berbiaya rendah yang dapat membuat perbedaan yang signifikan selama Anda hanya bisa menjamin karakter ASCII.

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

Pada prosesor berbiaya rendah yang sederhana, biasanya, operasi bitwise jauh lebih cepat daripada pembagian, beberapa kali lebih cepat dari multiplikasi, dan kadang-kadang secara signifikan lebih cepat daripada penambahan.

CATATAN: Saya akan merekomendasikan menggunakan perpustakaan standar untuk bekerja dengan string karena sejumlah alasan (keterbacaan, kebenaran, portabilitas, dll). Hanya gunakan sedikit membalik jika Anda telah mengukur kinerja dan ini adalah hambatan Anda.

Brian
sumber
14

Begitulah cara kerja ASCII, itu saja.

Tetapi dalam mengeksploitasi ini, Anda memberikan portabilitas karena C ++ tidak menuntut ASCII sebagai encoding.

Inilah sebabnya mengapa fungsi std::toupperdan std::tolowerdiimplementasikan dalam pustaka standar C ++ - Anda harus menggunakannya.

Batsyeba
sumber
6
Ada beberapa protokol, yang mengharuskan ASCII digunakan, seperti DNS. Bahkan, "trik 0x20" digunakan oleh beberapa server DNS untuk memasukkan entropi tambahan ke permintaan DNS sebagai mekanisme anti-spoofing. DNS tidak peka huruf besar-kecil, tetapi juga seharusnya melindungi huruf besar-kecil, jadi jika mengirim kueri dengan huruf besar-kecil dan mendapatkan huruf besar kembali, ini merupakan indikasi yang baik bahwa respons belum dipalsukan oleh pihak ketiga.
Alnitak
Perlu disebutkan bahwa banyak pengkodean masih memiliki representasi yang sama untuk karakter ASCII standar (tidak diperpanjang). Tapi tetap saja, jika Anda benar-benar khawatir tentang pengkodean yang berbeda, Anda harus menggunakan fungsi yang tepat.
Kapten Man
5
@ Kapten Pria: Tentu saja. UTF-8 adalah keindahan semata. Mudah-mudahan itu "diserap" ke dalam standar C ++ sejauh IEEE754 miliki untuk floating point.
Batsyeba
11

Lihat tabel kedua di http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii , dan catatan berikut, direproduksi di bawah:

Pengubah Kontrol pada keyboard Anda pada dasarnya membersihkan tiga bit teratas dari karakter apa pun yang Anda ketikkan, meninggalkan lima terbawah dan memetakannya ke kisaran 0..31. Jadi, misalnya, Ctrl-SPACE, Ctrl- @, dan Ctrl-`semuanya memiliki arti yang sama: NUL.

Keyboard yang sangat lama digunakan untuk melakukan Shift hanya dengan mengaktifkan 32 atau 16 bit, tergantung pada tombolnya; Inilah sebabnya mengapa hubungan antara huruf kecil dan huruf kapital dalam ASCII sangat teratur, dan hubungan antara angka dan simbol, dan beberapa pasang simbol, adalah hal yang biasa jika Anda menyipit. ASR-33, yang merupakan terminal huruf besar semua, bahkan memungkinkan Anda menghasilkan beberapa karakter tanda baca yang tidak memiliki kunci untuknya dengan menggeser 16 bit; dengan demikian, misalnya, Shift-K (0x4B) menjadi [(0x5B)

ASCII dirancang sedemikian rupa sehingga tombol keyboard shiftdan ctrldapat diimplementasikan tanpa banyak (atau mungkin ada ctrl) logika - shiftmungkin diperlukan hanya beberapa gerbang. Mungkin masuk akal setidaknya untuk menyimpan protokol kawat seperti pengkodean karakter lainnya (tidak diperlukan konversi perangkat lunak).

Artikel yang ditautkan juga menjelaskan banyak konvensi peretas aneh seperti And control H does a single character and is an old^H^H^H^H^H classic joke.( ditemukan di sini ).

Iiridayn
sumber
1
Dapat menerapkan toggle shift untuk lebih banyak ASCII tanpa foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20, meskipun ini hanya ASCII dan karenanya tidak bijaksana karena alasan yang dinyatakan dalam jawaban lain. Mungkin juga dapat ditingkatkan dengan pemrograman bebas cabang.
Iiridayn
1
Ah, foo ^= 0x20 >> !(foo & 0x40)akan lebih sederhana. Juga contoh bagus mengapa kode singkat sering dianggap tidak dapat dibaca ^ _ ^.
Iiridayn
8

Xoring dengan 32 (00100000 dalam biner) menetapkan atau mengatur ulang bit keenam (dari kanan). Ini sangat setara dengan menambah atau mengurangi 32.

Yves Daoust
sumber
2
Cara lain untuk mengatakan ini adalah bahwa XOR adalah add-without-carry.
Peter Cordes
7

Rentang alfabet huruf kecil dan huruf besar tidak melewati batas %32"alignment" dalam sistem pengkodean ASCII.

Inilah sebabnya mengapa bit 0x20adalah satu-satunya perbedaan antara versi huruf besar / kecil dari huruf yang sama.

Jika ini bukan masalahnya, Anda harus menambah atau mengurangi 0x20, bukan hanya beralih, dan untuk beberapa huruf akan ada tugas untuk membalik bit lain yang lebih tinggi. (Dan tidak akan ada satu operasi yang bisa beralih, dan memeriksa karakter alfabet di tempat pertama akan lebih sulit karena Anda tidak bisa | = 0x20 untuk memaksa lcase.)


Trik khusus ASCII terkait: Anda dapat memeriksa karakter ASCII alfabet dengan memaksa huruf kecil dengan c |= 0x20dan kemudian memeriksa apakah (tidak ditandatangani) c - 'a' <= ('z'-'a'). Jadi hanya 3 operasi: ATAU + SUB + CMP terhadap konstan 25. Tentu saja, kompiler tahu bagaimana mengoptimalkan (c>='a' && c<='z') ke asm seperti ini untuk Anda , jadi paling-paling Anda harus melakukan c|=0x20bagian sendiri. Agak merepotkan untuk melakukan sendiri semua pengecoran yang diperlukan, terutama untuk mengerjakan promosi bilangan bulat default yang akan ditandatangani 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

Lihat juga Konversi String Dalam C ++ Ke Huruf Besar (string SIMD toupperhanya untuk ASCII, menutupi operand untuk XOR menggunakan centang itu.)

Dan juga Cara mengakses array char dan mengubah huruf kecil ke huruf besar, dan sebaliknya (C dengan SIMD intrinsik, dan skalar x86 asm case-flip untuk karakter ASCII alfabet, membuat yang lain tidak dimodifikasi.)


Trik-trik ini sebagian besar hanya berguna jika tangan mengoptimalkan beberapa pemrosesan teks dengan SIMD (misalnya SSE2 atau NEON), setelah memeriksa bahwa tidak ada chardalam vektor memiliki bit set tinggi. (Dan dengan demikian tidak ada byte yang merupakan bagian dari multi-byte UTF-8 encoding untuk satu karakter, yang mungkin memiliki invers huruf besar / kecil yang berbeda). Jika Anda menemukannya, Anda dapat kembali ke skalar untuk potongan 16 byte ini, atau untuk sisa string.

Bahkan ada beberapa lokal tempat toupper()atau tolower()pada beberapa karakter dalam rentang ASCII menghasilkan karakter di luar rentang itu, terutama Turki di mana saya ↔ ı dan İ ↔ i. Di lokasi tersebut, Anda memerlukan pemeriksaan yang lebih canggih, atau mungkin tidak mencoba menggunakan pengoptimalan ini sama sekali.


Tetapi dalam beberapa kasus, Anda diizinkan untuk menggunakan ASCII alih-alih UTF-8, mis. Utilitas Unix with LANG=C(the POSIX locale), bukan en_CA.UTF-8atau apa pun.

Tetapi jika Anda bisa memastikan keamanannya, Anda dapat membuat toupperstring berukuran sedang jauh lebih cepat daripada menelepon toupper()dalam satu lingkaran (seperti 5x), dan terakhir saya uji dengan Boost 1.58 , jauh lebih cepat daripada boost::to_upper_copy<char*, std::string>()yang dilakukan bodoh dynamic_castuntuk setiap karakter.

Peter Cordes
sumber