Ý tưởng đằng sau ^ = 32 là gì, chuyển đổi chữ thường thành chữ hoa và ngược lại?


146

Tôi đã giải quyết một số vấn đề về tiền mã hóa. Thông thường trước tiên tôi kiểm tra xem ký tự đó là chữ cái tiếng Anh trên hay dưới, sau đó trừ hoặc thêm 32để chuyển đổi nó thành chữ cái tương ứng. Nhưng tôi đã tìm thấy ai đó làm ^= 32điều tương tự. Đây là:

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

Tôi đã tìm kiếm một lời giải thích cho điều này và không tìm ra. Vậy tại sao điều này hoạt động?


5
vi.wikipedia.org/wiki/File:USASCII_code_chart.png Mẹo: bạn có thể chuyển đổi @thành `bằng cách sử dụng ^ 32.
KamilCuk

112
FWIW, nó không thực sự "hoạt động". Nó hoạt động cho bộ ký tự cụ thể này nhưng có những bộ khác mà bạn sẽ không nên sử dụng touppertolowerchuyển đổi vỏ.
NathanOliver

7
đôi khi với các cuộc thi trực tuyến "ý tưởng" là viết mã theo cách khó hiểu đến mức nó sẽ không bao giờ vượt qua một đánh giá nghiêm túc;)
idclev 463035818

21
^ = đang chuyển đổi giá trị bằng XOR. Các chữ cái viết hoa ASCII có số 0 trong bit tương ứng, trong khi các chữ cái viết thường có một. Điều đó nói rằng, xin đừng! Sử dụng các thói quen ký tự (unicode) thích hợp để chuyển đổi giữa chữ thường và chữ hoa. Thời đại của ASCII chỉ còn lâu.
Hans-Martin Mosner

14
Không chỉ là nó chỉ hoạt động với một số bộ ký tự. Thậm chí nếu chúng ta giả định tất cả thế giới là UTF-8 (mà có thể ít nhất là một mục tiêu không tưởng đẹp), nó cũng chỉ có tác dụng với 26 chữ cái Ađể Z. Điều đó tốt miễn là bạn chỉ quan tâm đến tiếng Anh (và không sử dụng cách viết "ngây thơ", những từ như "café", hoặc tên với dấu phụ ...), nhưng thế giới không chỉ là tiếng Anh.
ilkkachu

Câu trả lời:


149

Chúng ta hãy xem bảng mã ASCII ở dạng nhị phân.

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

Và 32 là 0100000sự khác biệt duy nhất giữa chữ thường và chữ in hoa. Vì vậy, bật tắt bit đó để thay đổi trường hợp của một lá thư.


49
"Chuyển đổi trường hợp" * chỉ dành cho ASCII
Vịt Mooing

39
@Mooing chỉ dành cho A-Za-z trong ASCII. Chữ thường của "[" không phải là "{".
dbkk

21
@dbkk {ngắn hơn [, vì vậy đây là trường hợp "thấp hơn". Không? Ok, tôi sẽ thể hiện ra: D
Peter Badida

25
Thông tin chi tiết: Trong khu vực 7 bit, các máy tính của Đức đã [] {|} được ánh xạ lại thành ÄÖÜäöü vì chúng tôi cần Umlauts nhiều hơn các ký tự đó, vì vậy trong bối cảnh đó, {(ä) thực sự chữ thường [(Ä).
Guntram Blohm hỗ trợ Monica

14
@GuntramBlohm Thông tin chi tiết khác, đây là lý do tại sao các máy chủ IRC coi foobar[]foobar{}là biệt danh giống hệt nhau, vì biệt danh không phân biệt chữ hoa chữ thường và IRC có nguồn gốc từ Scandinavia :)
ZeroKnight 7/2/19

117

Điều này sử dụng thực tế hơn các giá trị ASCII đã được lựa chọn bởi những người thực sự thông minh.

foo ^= 32;

Điều này lật bit 1 thấp nhất thứ 6 của foo(cờ chữ hoa của loại ASCII), chuyển đổi chữ hoa ASCII thành chữ thường và ngược lại .

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

Thí dụ

'A' ^ 32

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

Và bởi tài sản của XOR , 'a' ^ 32 == 'A'.

Để ý

C ++ không bắt buộc phải sử dụng ASCII để thể hiện các ký tự. Một biến thể khác là EBCDIC . Thủ thuật này chỉ hoạt động trên nền tảng ASCII. Một giải pháp di động hơn sẽ được sử dụng std::tolowerstd::toupper, với phần thưởng được cung cấp để nhận biết được địa phương (mặc dù nó không tự động giải quyết tất cả các vấn đề của bạn, xem bình luận):

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) Vì 32 là 1 << 5(2 đến lũy thừa 5), ​​nó lật bit thứ 6 (tính từ 1).


16
EBCDIC cũng được chọn bởi một số người rất thông minh: hoạt động thực sự độc đáo trên các thẻ đục lỗ ASCII là một mớ hỗn độn. Nhưng đây là một câu trả lời hay, +1.
Bathsheba

65
Tôi không biết về thẻ đục lỗ, nhưng ASCII đã được sử dụng trên băng giấy. Đó là lý do tại sao ký tự Xóa được mã hóa thành 1111111: Vì vậy, bạn có thể đánh dấu bất kỳ ký tự nào là "đã xóa" bằng cách đục tất cả các lỗ trên cột của nó trên băng.
dan04

23
@Bathsheba là một người chưa sử dụng thẻ đục lỗ, rất khó khăn để che giấu ý tưởng rằng EBCDIC được thiết kế thông minh.
Lord Farquaad

9
@LordFarquaad IMHO hình ảnh Wikipedia về cách các chữ cái được viết trên punchcard là một minh họa rõ ràng về cách EBCDIC thực hiện một số (nhưng không phải toàn bộ, xem / so với S) cho mã hóa này. vi.wikipedia.org/wiki/EBCDIC#/media/ Từ
Peteris

11
@ dan04 Lưu ý đề cập đến "dạng chữ thường của 'MASSE' là gì?". Đối với những người không biết, có hai từ tiếng Đức có dạng chữ hoa là MASSE; một là "Masse" và cái còn lại là "Maße". Thích hợp tolowerở Đức không chỉ đơn thuần cần một cuốn từ điển, nó cần phải có khả năng phân tích ý nghĩa.
Martin Bonner hỗ trợ Monica

35

Cho phép tôi nói rằng đây là - mặc dù nó có vẻ thông minh - một hack thực sự, thực sự ngu ngốc. Nếu ai đó khuyên bạn điều này vào năm 2019, hãy đánh anh ta. Đánh anh ta mạnh nhất có thể.
Tất nhiên, bạn có thể làm điều đó trong phần mềm của riêng bạn mà bạn và không ai khác sử dụng nếu bạn biết rằng bạn sẽ không bao giờ sử dụng bất kỳ ngôn ngữ nào ngoài tiếng Anh. Nếu không, không đi.

Vụ hack được cho là "OK" vào khoảng 30 - 35 năm trước khi máy tính không thực sự làm được gì nhiều ngoài tiếng Anh ở ASCII và có thể một hoặc hai ngôn ngữ chính của Châu Âu. Nhưng ... không còn như vậy nữa.

Vụ hack hoạt động vì chữ hoa và chữ thường của Mỹ-Latin cách 0x20nhau hoàn toàn và xuất hiện theo cùng một thứ tự, đó chỉ là một chút khác biệt. Trong thực tế, bit này hack, toggles.

Bây giờ, những người tạo ra các trang mã cho Tây Âu, và sau đó là tập đoàn Unicode, đã đủ thông minh để giữ sơ đồ này, ví dụ như Umlauts của Đức và Nguyên âm có dấu tiếng Pháp. Không phải như vậy đối với ß (cho đến khi ai đó thuyết phục được tập đoàn Unicode năm 2017 và một tạp chí in Tin tức giả lớn đã viết về nó, thực sự thuyết phục Duden - không có bình luận nào về điều đó) thậm chí không tồn tại như một câu nói khác (biến thành SS) . Bây giờ nó không tồn tại như Versal, nhưng hai 0x1DBFvị trí xa nhau, không 0x20.

Những người thực hiện, tuy nhiên, không đủ chu đáo để tiếp tục điều này. Ví dụ: nếu bạn áp dụng hack của mình bằng một số ngôn ngữ Đông Âu hoặc tương tự (tôi sẽ không biết về Cyrillic), bạn sẽ nhận được một bất ngờ khó chịu. Tất cả những ký tự "hatchet" là ví dụ về điều đó, chữ thường và chữ hoa là một khác nhau. Do đó, hack không hoạt động đúng ở đó.

Chẳng hạn, còn nhiều điều cần xem xét, chẳng hạn, một số ký tự không đơn giản chuyển đổi từ chữ thường sang chữ hoa (chúng được thay thế bằng các chuỗi khác nhau) hoặc chúng có thể thay đổi hình thức (yêu cầu các điểm mã khác nhau).

Thậm chí không nghĩ về việc hack này sẽ làm gì với những thứ như tiếng Thái hay tiếng Trung (nó sẽ khiến bạn vô lý hoàn toàn).

Việc tiết kiệm vài trăm chu kỳ CPU có thể rất đáng giá 30 năm trước, nhưng ngày nay, thực sự không có lý do gì để chuyển đổi một chuỗi đúng cách. Có các chức năng thư viện để thực hiện nhiệm vụ không tầm thường này.
Thời gian để chuyển đổi vài chục kilobyte văn bản đúng cách là không đáng kể ngày nay.


2
Tôi hoàn toàn đồng ý - mặc dù đó là một ý tưởng tốt cho mọi lập trình viên để biết lý do tại sao nó hoạt động - thậm chí có thể tạo ra một câu hỏi phỏng vấn tốt .. Điều này làm gì và khi nào nên sử dụng :)
Bill K

33

Nó hoạt động bởi vì, như nó xảy ra, sự khác biệt giữa 'a' và A 'trong ASCII và mã hóa dẫn xuất là 32 và 32 cũng là giá trị của bit thứ sáu. Lật bit thứ 6 bằng OR độc quyền do đó chuyển đổi giữa trên và dưới.


22

Nhiều khả năng việc triển khai bộ ký tự của bạn sẽ là ASCII. Nếu chúng ta nhìn vào bảng:

nhập mô tả hình ảnh ở đây

Chúng tôi thấy rằng có một sự khác biệt chính xác 32giữa giá trị của chữ thường và số chữ hoa. Do đó, nếu chúng ta làm ^= 32(tương đương với việc thay đổi bit quan trọng thứ 6), nó sẽ thay đổi giữa ký tự chữ thường và chữ hoa.

Lưu ý rằng nó hoạt động với tất cả các biểu tượng, không chỉ các chữ cái. Nó chuyển đổi một ký tự với ký tự tương ứng trong đó bit thứ 6 khác nhau, dẫn đến một cặp ký tự được chuyển qua lại giữa. Đối với các chữ cái, các ký tự viết hoa / in thường tương ứng tạo thành một cặp như vậy. A NULsẽ thay đổi thành Spacevà ngược lại, và các @toggles với backtick. Về cơ bản, bất kỳ ký tự nào trong cột đầu tiên trên biểu đồ này đều bật tắt với một cột ký tự và điều tương tự áp dụng cho cột thứ ba và thứ tư.

Mặc dù vậy, tôi sẽ không sử dụng bản hack này vì không đảm bảo rằng nó sẽ hoạt động trên bất kỳ hệ thống nào. Chỉ cần sử dụng touppertolower thay thế, và các truy vấn như isupper .


2
Chà, nó không hoạt động đối với tất cả các chữ cái có chênh lệch 32. Nếu không, nó sẽ hoạt động giữa '@' và ''!
Matthieu Brucher

2
@MatthieuBrucher Nó đang hoạt động, 32 ^ 32là 0, không phải 64
NathanOliver

5
'@' và '' không phải là "chữ cái". Chỉ [a-z][A-Z]là "chữ cái". Phần còn lại là sự trùng hợp tuân theo quy tắc tương tự. Nếu ai đó yêu cầu bạn "viết hoa]", đó sẽ là gì? nó vẫn sẽ là "]" - "}" không phải là "chữ hoa" của "]".
Freedomn-m

4
@MatthieuBrucher: Một cách khác để đưa ra quan điểm đó là phạm vi chữ cái viết thường và viết thường không vượt qua %32ranh giới "căn chỉnh" trong hệ thống mã hóa ASCII. Đây là lý do tại sao bit 0x20là sự khác biệt duy nhất giữa các phiên bản chữ hoa / thường của cùng một chữ cái. Nếu đây không phải là trường hợp, bạn cần thêm hoặc bớt 0x20, không chỉ chuyển đổi và đối với một số chữ cái sẽ có thể thực hiện để lật các bit cao hơn khác. (Và thao tác tương tự không thể chuyển đổi và việc kiểm tra các ký tự chữ cái ở vị trí đầu tiên sẽ khó hơn vì bạn không |= 0x20thể ép buộc.)
Peter Cordes

2
+1 để nhắc nhở tôi về tất cả các lượt truy cập vào asciitable.com để nhìn chằm chằm vào đồ họa chính xác đó (và phiên bản ASCII mở rộng !!) cho lần cuối, tôi không biết, 15 hay 20 năm nữa?
AC

15

Rất nhiều câu trả lời hay ở đây mô tả cách thức hoạt động của nó, nhưng tại sao nó hoạt động theo cách này là để cải thiện hiệu suất. Các thao tác bitwise nhanh hơn hầu hết các hoạt động khác trong bộ xử lý. Bạn có thể nhanh chóng thực hiện so sánh không phân biệt chữ hoa chữ thường bằng cách không nhìn vào bit xác định trường hợp hoặc thay đổi trường hợp thành trên / dưới chỉ bằng cách lật bit (những kẻ đã thiết kế bảng ASCII khá thông minh).

Rõ ràng, điều này gần như không phải là một thỏa thuận lớn như ngày nay khi nó trở lại vào năm 1960 (khi công việc bắt đầu trên ASCII) do bộ xử lý và Unicode nhanh hơn, nhưng vẫn còn một số bộ xử lý giá rẻ có thể tạo ra sự khác biệt đáng kể miễn là bạn chỉ có thể đảm bảo các ký tự ASCII.

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

Trên các bộ xử lý chi phí thấp đơn giản, thông thường, các thao tác bitwise nhanh hơn đáng kể so với phép chia, nhanh hơn nhiều lần so với phép nhân và đôi khi nhanh hơn đáng kể so với phép cộng.

LƯU Ý: Tôi sẽ khuyên bạn nên sử dụng các thư viện tiêu chuẩn để làm việc với các chuỗi vì một số lý do (khả năng đọc, tính chính xác, tính di động, v.v.). Chỉ sử dụng lật bit nếu bạn đã đo hiệu suất và đây là nút cổ chai của bạn.


14

Đó là cách ASCII hoạt động, đó là tất cả.

Nhưng khi khai thác điều này, bạn đang từ bỏ tính di động vì C ++ không khăng khăng coi ASCII là mã hóa.

Đây là lý do tại sao các hàm std::toupperstd::tolowerđược triển khai trong thư viện chuẩn C ++ - thay vào đó bạn nên sử dụng các hàm đó.


6
Mặc dù có các giao thức, yêu cầu ASCII được sử dụng, chẳng hạn như DNS. Trên thực tế, "thủ thuật 0x20" được một số máy chủ DNS sử dụng để chèn entropy bổ sung vào truy vấn DNS dưới dạng cơ chế chống giả mạo. DNS không phân biệt chữ hoa chữ thường, nhưng cũng được coi là bảo quản trường hợp, vì vậy nếu gửi truy vấn với trường hợp ngẫu nhiên và nhận lại trường hợp tương tự thì đó là một dấu hiệu tốt cho thấy phản hồi đã không bị bên thứ ba giả mạo.
Alnitak

Điều đáng nói là rất nhiều bảng mã vẫn có cùng biểu diễn cho các ký tự ASCII tiêu chuẩn (không được mở rộng). Tuy nhiên, nếu bạn thực sự lo lắng về các bảng mã khác nhau, bạn nên sử dụng các hàm thích hợp.
Thuyền trưởng Man

5
@CaptainMan: Hoàn toàn đúng. UTF-8 là một thứ của vẻ đẹp tuyệt đối. Hy vọng rằng nó sẽ được "hấp thụ" vào trong tiêu chuẩn C ++ mà IEEE754 có cho điểm nổi.
Bathsheba

11

Xem bảng thứ hai tại http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii và các ghi chú sau, được sao chép dưới đây:

Công cụ sửa đổi Điều khiển trên bàn phím của bạn về cơ bản sẽ xóa ba bit trên cùng của bất kỳ ký tự nào bạn nhập, để lại năm phần dưới cùng và ánh xạ nó đến phạm vi 0..31. Vì vậy, ví dụ, Ctrl-SPACE, Ctrl- @ và Ctrl-`đều có nghĩa giống nhau: NUL.

Bàn phím rất cũ được sử dụng để thực hiện Shift chỉ bằng cách bật 32 hoặc 16 bit, tùy thuộc vào phím; đây là lý do tại sao mối quan hệ giữa chữ nhỏ và chữ in hoa trong ASCII rất thường xuyên và mối quan hệ giữa số và ký hiệu và một số cặp ký hiệu là loại thường xuyên nếu bạn nheo mắt nhìn nó. ASR-33, là một thiết bị đầu cuối chữ hoa, thậm chí cho phép bạn tạo một số ký tự dấu chấm câu mà nó không có khóa để thay đổi 16 bit; do đó, ví dụ, Shift-K (0x4B) đã trở thành [(0x5B)

ASCII được thiết kế sao cho các phím shiftctrlbàn phím có thể được thực hiện mà không cần nhiều ctrllogic (hoặc có thể là bất kỳ ) nào - shiftcó lẽ chỉ cần một vài cổng. Có lẽ nó có ý nghĩa ít nhất là lưu trữ giao thức dây như bất kỳ mã hóa ký tự nào khác (không yêu cầu chuyển đổi phần mềm).

Bài viết được liên kết cũng giải thích nhiều quy ước về hacker kỳ lạ như And control H does a single character and is an old^H^H^H^H^H classic joke.( tìm thấy ở đây ).


1
Có thể thực hiện chuyển đổi thay đổi để có thêm ASCII w / foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20, mặc dù đây chỉ là ASCII và do đó không khôn ngoan vì những lý do được nêu trong các câu trả lời khác. Nó có thể cũng có thể được cải thiện với chương trình không có chi nhánh.
Iiridayn

1
Ah, foo ^= 0x20 >> !(foo & 0x40)sẽ đơn giản hơn. Cũng là một ví dụ tốt về lý do tại sao mã terse thường được coi là không thể đọc được ^ _ ^.
Iiridayn

8

Xored với 32 (00100000 trong nhị phân) đặt hoặc đặt lại bit thứ sáu (từ bên phải). Điều này hoàn toàn tương đương với việc cộng hoặc trừ 32.


2
Một cách khác để nói điều này là XOR là add-without-carry.
Peter Cordes

7

Phạm vi chữ cái viết thường và viết thường không vượt qua %32ranh giới "căn chỉnh" trong hệ thống mã hóa ASCII.

Đây là lý do tại sao bit 0x20là sự khác biệt duy nhất giữa các phiên bản chữ hoa / thường của cùng một chữ cái.

Nếu đây không phải là trường hợp, bạn cần thêm hoặc bớt 0x20, không chỉ chuyển đổi và đối với một số chữ cái sẽ có thể thực hiện để lật các bit cao hơn khác. (Và sẽ không có một thao tác nào có thể chuyển đổi và việc kiểm tra các ký tự chữ cái ở vị trí đầu tiên sẽ khó hơn vì bạn không thể | = 0x20 để ép buộc.)


Các thủ thuật chỉ liên quan đến ASCII: bạn có thể kiểm tra ký tự ASCII chữ cái bằng cách buộc chữ thường c |= 0x20và sau đó kiểm tra xem (không dấu) c - 'a' <= ('z'-'a'). Vì vậy, chỉ có 3 thao tác: HOẶC + SUB + CMP theo hằng số 25. Tất nhiên, trình biên dịch biết cách tối ưu hóa (c>='a' && c<='z') thành asm như thế này cho bạn , vì vậy, nhiều nhất bạn nên c|=0x20tự mình thực hiện phần này. Thật bất tiện khi tự mình thực hiện tất cả các lần truyền cần thiết, đặc biệt là làm việc xung quanh các chương trình khuyến mãi số nguyên mặc định để ký 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

Xem thêm Chuyển đổi một chuỗi trong C ++ sang chữ hoa (chuỗi SIMD toupperchỉ cho ASCII, che dấu toán hạng cho XOR bằng cách sử dụng kiểm tra đó.)

Và cũng như Cách truy cập một mảng char và thay đổi chữ in thường thành chữ in hoa và ngược lại (C với nội tại SIMD, và lật x8 asm case-flip cho các ký tự ASCII chữ cái, không để lại chữ cái khác.)


Các thủ thuật này hầu như chỉ hữu ích nếu tối ưu hóa một số xử lý văn bản bằng SIMD (ví dụ SSE2 hoặc NEON), sau khi kiểm tra xem không có chars nào trong vectơ có bit cao được đặt. (Và do đó, không có byte nào là một phần của mã hóa UTF-8 nhiều byte cho một ký tự, có thể có các nghịch đảo chữ hoa / chữ thường khác nhau). Nếu bạn tìm thấy bất kỳ, bạn có thể quay lại vô hướng cho đoạn 16 byte này hoặc cho phần còn lại của chuỗi.

Thậm chí có một số địa phương nơi toupper()hoặc tolower()trên một số ký tự trong phạm vi ASCII tạo ra các ký tự nằm ngoài phạm vi đó, đáng chú ý là tiếng Thổ Nhĩ Kỳ nơi tôi và tôi. Ở những địa phương đó, bạn cần kiểm tra tinh vi hơn hoặc có thể không cố gắng sử dụng tối ưu hóa này.


Nhưng trong một số trường hợp, bạn được phép sử dụng ASCII thay vì UTF-8, ví dụ: các tiện ích Unix có LANG=C(ngôn ngữ POSIX), không en_CA.UTF-8hoặc bất cứ điều gì.

Nhưng nếu bạn có thể xác minh đó là an toàn, bạn có thể toupperchuỗi dài vừa nhanh hơn nhiều so với gọi toupper()trong một vòng lặp (như 5x), và tôi kiểm tra lần cuối với Boost 1,58 , nhiều nhiều nhanh hơn boost::to_upper_copy<char*, std::string>()mà không một ngu ngốc dynamic_castcho mỗi nhân vật.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.