소문자를 대문자로 또는 그 반대로 변환하는 ^ = 32의 아이디어는 무엇입니까?


146

codeforces에서 몇 가지 문제를 해결하고있었습니다. 일반적으로 먼저 문자가 영문 또는 대문자인지 확인한 다음 빼거나 추가 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, 그것은 실제로 "작동"하지 않습니다. 이 특정 문자 세트에는 작동하지만 케이스를 사용 toupper하고 tolower전환 하지 않아야하는 다른 세트가 있습니다 .
NathanOliver

7
온라인 콘테스트와 함께 언젠가 "아이디어"는 진지한 검토를하지 않을 정도로 난독 화 된 방식으로 코드를 작성하는 것입니다;)
idclev 463035818

21
^ =는 XOR을 사용하여 값을 변환합니다. 대문자 ASCII 문자는 해당 비트에서 0을, 소문자는 1을 갖습니다. 그 말은하지 마십시오! 적절한 문자 (유니 코드) 루틴을 사용하여 소문자와 대문자를 변환하십시오. ASCII의 시대는 오래 전부터 사라졌습니다.
Hans-Martin Mosner

14
단지 일부 문자 세트에서만 작동하는 것이 아닙니다. 모든 세계가 UTF-8 (최소한 유토피아 목표 일 수 있음)이라고 가정하더라도 26 글자로만 작동 A합니다 Z. 영어에만 관심이 있고 ( "naïve"와 같은 단어, "café"와 같은 단어 또는 분음 부호가있는 이름을 사용하지 않는 등) 괜찮지 만 세상은 영어 만이 아닙니다.
ilkkachu 2019

답변:


149

ASCII 코드 테이블을 바이너리로 살펴 보자.

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

32는 0100000소문자와 대문자의 유일한 차이점입니다. 따라서 비트를 토글하면 문자의 대소 문자가 토글됩니다.


49
"사례 전환"* ASCII 전용
Mooing Duck

39
@ ASCII에서 A-Za-z에 대해서만 무음. "["의 소문자는 "{" 이 아닙니다 .
dbkk

21
@dbkk {는보다 짧으므로 ["낮은"경우입니다. 아니? 좋아, 나는 나 자신을 보여줄 것이다 : D
Peter Badida

25
하찮은 재미있는 이야기가 : 7 비트 영역에서 독일 컴퓨터가 있었다 [] {|} 우리는 더 많은 사람들 자보다 움라우트 필요하기 때문에 그래서 맥락에서, ÄÖÜäöü에 매핑, {(A) 실제로 이었다 소문자 [(A)는.
Guntram Blohm은 Monica를 지원합니다 : Monica

14
@GuntramBlohm 더 자세한 사소한 일이기 때문에 , 닉네임은 대소 문자를 구분하지 않고 IRC는 스칸디나비아에 기원을두고 있기 때문에 IRC 서버 가 동일한 닉네임을 고려foobar[] 하고 foobar{}동일한 이유입니다.
ZeroKnight

117

이것은 실제로 똑똑한 사람들이 선택한 ASCII 값보다 사실을 사용합니다.

foo ^= 32;

제 6 최하위 비트 뒤집 하나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::tolowerstd::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.
Bathsheba

65
펀치 카드는 모르지만 종이 테이프 에는 ASCII 사용되었습니다. 그렇기 때문에 Delete 문자는 1111111로 인코딩됩니다. 따라서 테이프의 열에있는 모든 구멍을 뚫어 문자를 "삭제됨"으로 표시 할 수 있습니다.
dan04

23
@Bathsheba는 펀치 카드를 사용하지 않은 사람으로서 EBCDIC이 지능적으로 설계되었다는 생각에 머리를 감는 것은 매우 어렵습니다.
Lord Farquaad

9
@LordFarquaad IMHO 펀치 카드에 문자를 쓰는 방법에 대한 Wikipedia 그림은 EBCDIC이이 인코딩에 대해 어떤 의미를 갖지만 (총 S / S는 아님) 어떻게 명백한지를 보여줍니다. en.wikipedia.org/wiki/EBCDIC#/media/…
Peteris

11
@ dan04 "소문자 'MASSE'는 무엇입니까?" 모르는 사람들을 위해 독일어에는 대문자가 MASSE 인 단어가 있습니다. 하나는 "Masse"이고 다른 하나는 "Maße"입니다. tolower독일어에서 적절한 것은 사전을 필요로 할뿐만 아니라 그 의미를 파싱 할 수 있어야합니다.
Martin Bonner는 Monica를 지원합니다. Monica

35

이것이 똑똑해 보이지만 실제로는 정말 어리석은 핵이라고 말할 수 있습니다. 누군가 2019 년에 당신에게 이것을 추천한다면, 그를 때리십시오. 당신이 할 수있는 한 열심히 그를 때려.
물론 어쨌든 영어 이외의 다른 언어를 사용하지 않을 것이라는 것을 알고 있다면 자신과 다른 사람이 사용하지 않는 소프트웨어를 자신의 소프트웨어에서 사용할 수 있습니다. 그렇지 않으면 갈 수 없습니다.

해킹은 컴퓨터가 정말하지 않았다 ASCII에 많이 있지만 영어를 수행 할 때 일부 30~35년 전에 "OK"논쟁의 여지가, 그리고 어쩌면 하나 또는 두 개의 주요 유럽 언어. 하지만 ... 더 이상은 그렇지 않습니다.

해킹은 US-Latin 대문자와 소문자가 서로 정확히 0x20떨어져 있고 동일한 순서로 나타나기 때문에 한 비트 차이이므로 작동합니다. 실제로이 비트 핵은 토글됩니다.

이제 서유럽 및 나중에 유니 코드 컨소시엄을위한 코드 페이지를 작성하는 사람들은 독일 움라우트 (Umlauts) 및 프랑스 식 모음과 같은 체계를 유지할만큼 똑똑했습니다. 그리 (사람이 2017 년 유니 코드 컨소시엄을 확신 할 때까지, 실제로 Duden 설득, 그것에 대해 쓴 많은 가짜 뉴스 인쇄 잡지 - 그에 노 코멘트)을 ß에 대한 도 존재하지 않는 versal로 (SS로 변환) . 지금은 않는 등 versal 존재하지만, 두는 0x1DBF떨어져 위치하지 0x20.

그러나 구현 자들은 이것을 계속할만큼 충분히 배려 하지 않았다 . 예를 들어, 동유럽 언어 등에서 해킹을 적용하면 (키릴 자모에 대해서는 몰랐습니다) 놀라 울 정도입니다. 이러한 "도끼"문자는 그 예이며 소문자와 대문자는 서로 다릅니다. 따라서 해킹이 제대로 작동 하지 않습니다 .

예를 들어, 일부 문자는 단순히 소문자에서 대문자로 변환되지 않거나 (서로 다른 시퀀스로 대체 됨) 형식이 변경 될 수 있습니다 (서로 다른 코드 포인트 필요).

이 핵이 태국이나 중국과 같은 것들에 대해 어떻게 할 것인지 생각조차하지 마십시오 (완전한 말도 안됩니다).

수백 개의 CPU 사이클을 절약하는 것은 30 년 전에는 매우 가치가 있었지만 현재는 문자열을 올바르게 변환 할 수있는 변명이 없습니다. 이 사소한 작업을 수행하기위한 라이브러리 기능이 있습니다. 오늘날
수십 킬로바이트의 텍스트를 올바르게 변환하는 데 걸리는 시간 은 무시할 만합니다.


2
작동하는 이유를 알고 모든 프로그래머를위한 좋은 아이디어이지만 - - 심지어 좋은 인터뷰 질문을 할 수있는이 할 무엇을 ... 때 그것은 :)을 사용해야 난 완전히 동의
빌 K

33

ASCII와 파생 인코딩에서 'a'와 A '의 차이는 32이고 32는 6 번째 비트의 값이기 때문에 작동합니다. 배타적 OR로 6 번째 비트를 뒤집 으면 상한과 하한 사이에서 변환됩니다.


22

문자 집합의 구현은 ASCII 일 가능성이 큽니다. 우리가 테이블을 보면 :

여기에 이미지 설명을 입력하십시오

우리 32는 소문자와 대문자의 값이 정확히 다르다는 것을 알 수 있습니다. 따라서 우리가 할 경우 ^= 32(6 번째 최하위 비트를 토글하는 것과 동일) 소문자와 대문자 사이에서 변경됩니다.

문자뿐만 아니라 모든 기호와 함께 작동합니다. 6 번째 비트가 다른 각각의 문자로 문자를 토글하여 한 쌍의 문자가 앞뒤로 토글됩니다. 문자의 경우 각각의 대문자 / 소문자가 이러한 쌍을 형성합니다. A NULSpace다른 방향으로 바뀌고 @백틱으로 토글됩니다. 기본적으로이 차트의 첫 번째 열에있는 모든 문자는 한 열 위에있는 문자로 전환되며 세 번째 및 네 번째 열에도 동일하게 적용됩니다.

그래도 어떤 시스템에서도 작동한다는 보장이 없기 때문에이 핵을 사용하지는 않을 것입니다. 그냥 사용 의 ToUppertolower를을 대신하고, 같은 쿼리 isupper .


2
32의 차이가있는 모든 문자에 대해서는 작동하지 않습니다. 그렇지 않으면 '@'와 ''사이에서 작동합니다!
Matthieu Brucher 2014

2
@MatthieuBrucher 그것은 작동하는 32 ^ 3264입니다 0 아님
NathanOliver

5
'@'및 ''는 '글자'가 아닙니다. 만 [a-z][A-Z]"문자"입니다. 나머지는 같은 규칙을 따르는 우연의 일치입니다. 누군가가 당신에게 "대문자]"를 물었다면 무엇일까요? 여전히 "]"입니다. "}"는 "]"의 "대문자"가 아닙니다.
freedomn-m

4
@MatthieuBrucher :이 점을 결정하는 또 다른 방법은 소문자와 대문자 알파벳 범위가 %32ASCII 코딩 시스템에서 "정렬"경계를 넘지 않는다는 것 입니다. 이것이0x20 동일한 문자의 대문자 / 소문자 버전간에 비트 가 유일한 차이점 인 이유 입니다. 그렇지 않은 경우에는 0x20토글뿐만 아니라을 더하거나 빼야 하며 일부 문자의 경우 다른 높은 비트를 뒤집기 위해 수행해야합니다. (그리고 동일한 작업을 토글 할 수 없었으며, |= 0x20lcase를 강제 할 수 없었기 때문에 처음부터 알파벳 문자를 확인하는 것이 더 어려울 것 입니다.)
Peter Cordes

2
지난 1 년 동안 정확한 그래픽 (그리고 확장 된 ASCII 버전 !!)을 쳐다보기 위해 asciitable.com을 방문한 것을 상기시켜주는 +1?
AC

15

여기에 이것이 작동하는 방법을 설명하는 좋은 답변이 많지만 왜 이렇게 작동하는지는 성능을 향상시키는 것입니다. 비트 단위 연산은 프로세서 내 대부분의 다른 연산보다 빠릅니다. 대소 문자를 결정하는 비트를 보지 않고 단순히 비트를 뒤집어 대 / 소문자를 대 / 소문자로 변경하여 대소 문자를 구분하지 않는 비교를 신속하게 수행 할 수 있습니다 (ASCII 테이블을 디자인 한 사람들은 꽤 똑똑했습니다).

분명히 이것은 빠른 프로세서와 유니 코드로 인해 1960 년 (ASCII에서 작업을 시작했을 때)으로 돌아 왔을 때 오늘날 큰 문제는 아니지만 여전히 상당한 차이를 만들 수있는 저렴한 프로세서가 있습니다. ASCII 문자 만 보장 할 수있는 한.

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

간단한 저비용 프로세서에서는 일반적으로 비트 단위 연산이 나누기보다 훨씬 빠르며 곱셈보다 몇 배 빠르며 때로는 덧셈보다 훨씬 빠릅니다.

참고 : 여러 가지 이유로 (가독성, 정확성, 이식성 등) 문자열 작업에 표준 라이브러리를 사용하는 것이 좋습니다. 성능을 측정했으며 이것이 병목 현상 인 경우에만 비트 뒤집기를 사용하십시오.


14

이것이 ASCII가 작동하는 방식입니다.

그러나 이것을 이용하면 C ++이 ASCII를 인코딩으로 주장하지 않기 때문에 이식성 을 포기합니다 .

이것이 함수 std::toupperstd::tolowerC ++ 표준 라이브러리에서 구현되는 이유입니다. 대신이 함수 를 사용해야합니다.


6
그러나 DNS와 같은 ASCII를 사용해야하는 프로토콜이 있습니다. 실제로 일부 DNS 서버에서는 "0x20 트릭"을 사용하여 스푸핑 방지 메커니즘으로 DNS 쿼리에 추가 엔트로피를 삽입합니다. DNS는 대 / 소문자를 구분하지 않지만 대 / 소문자를 보존해야하므로 임의의 대 / 소문자를 사용하여 쿼리를 보내고 동일한 대 / 소문자를 다시 가져 오는 경우 타사에서 응답을 스푸핑하지 않았 음을 나타냅니다.
Alnitak

많은 인코딩이 여전히 표준 (확장되지 않은) ASCII 문자에 대해 동일한 표현을 가지고 있음을 언급 할 가치가 있습니다. 그러나 여전히 다른 인코딩이 걱정된다면 올바른 기능을 사용해야합니다.
캡틴 맨

5
@CaptainMan : 물론입니다. UTF-8은 순수한 아름다움입니다. IEEE754가 부동 소수점을 갖는 한 C ++ 표준에 "흡수"되기를 바랍니다.
Bathsheba

11

http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii 의 두 번째 표 와 아래 참고 사항을 참조하십시오.

키보드의 Control 수정자는 기본적으로 입력하는 문자의 상위 3 비트를 지우고 하위 5 개는 그대로두고 0..31 범위에 매핑합니다. 예를 들어 Ctrl-SPACE, Ctrl- @ 및 Ctrl-`는 모두 NUL과 같은 의미입니다.

아주 오래된 키보드는 키에 따라 32 비트 또는 16 비트를 토글하여 Shift 키를 사용했습니다. 그렇기 때문에 ASCII에서 소문자와 대문자 사이의 관계는 매우 규칙적이며 숫자와 기호, 일부 기호 쌍 사이의 관계는 삐걱 거리면 규칙적입니다. 모든 대문자 터미널 인 ASR-33을 사용하면 16 비트를 시프트하여 키가없는 문장 부호 문자를 생성 할 수도 있습니다. 따라서 예를 들어 Shift-K (0x4B)는 [(0x5B)

ASCII는 키보드 키 shiftctrl키보드 키를 ctrl로직 없이 구현할 수 있도록 설계되었습니다. 아마도 shift몇 개의 게이트 만 필요했을 것입니다. 유선 프로토콜을 다른 문자 인코딩 (소프트웨어 변환 필요 없음)만큼 저장하는 것이 좋습니다.

링크 된 기사 And control H does a single character and is an old^H^H^H^H^H classic joke. ( here에서 찾을 수있는 ) 와 같은 많은 이상한 해커 규칙을 설명합니다 .


1
더 많은 ASCII w /에 대해 전환 토글을 구현할 수 foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20있지만 이것은 ASCII 일 뿐이므로 다른 답변에 명시된 이유로 현명하지 않습니다. 브랜치없는 프로그래밍으로 개선 될 수도 있습니다.
Iiridayn

1
아, foo ^= 0x20 >> !(foo & 0x40)더 간단합니다. 왜 간결한 코드가 종종 읽을 수없는 ^ _ ^로 간주되는지에 대한 좋은 예입니다.
Iiridayn

8

32 (2 진에서 00100000)로 Xoring하면 6 번째 비트 (오른쪽에서)를 설정하거나 재설정합니다. 이것은 32를 더하거나 빼는 것과 완전히 같습니다.


2
이것을 말하는 또 다른 방법은 XOR이 부가 기능이 없다는 것입니다.
Peter Cordes 2019

7

소문자 및 대문자 알파벳 범위는 %32ASCII 코딩 시스템에서 "정렬"경계를 넘지 않습니다 .

그렇기 때문에 비트 0x20가 동일한 문자의 대문자 / 소문자 버전 간의 유일한 차이점입니다.

그렇지 않은 경우에는 0x20토글뿐만 아니라을 더하거나 빼야 하며 일부 문자의 경우 다른 높은 비트를 뒤집기 위해 수행해야합니다. (그리고 토글 할 수있는 단일 조작이 없으며, lcase를 강제하기 위해 | = 0x20을 사용할 수 없기 때문에 처음에 알파벳 문자를 확인하는 것이 더 어려울 것입니다.)


관련 ASCII 전용 트릭 : 소문자를 강제로 입력하고 (부호없는) 여부를 확인 하여 알파벳 ASCII 문자c |= 0x20 를 확인할 수 c - 'a' <= ('z'-'a')있습니다. 따라서 상수 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 case-flip, 다른 문자는 수정하지 않음)


이 트릭은 SIMD (예 : SSE2 또는 NEON)를 사용하여 일부 텍스트 처리를 수동으로 최적화하는 경우에만 유용합니다 char. 벡터 의 s 중 어느 것도 높은 비트 세트를 가지고 있지 않은지 확인합니다 . (따라서 바이트 중 어느 것도 단일 문자에 대한 멀티 바이트 UTF-8 인코딩의 일부가 아니며 다른 대문자 / 소문자 역수를 가질 수 있습니다). 발견하면 16 바이트의 청크 또는 나머지 문자열에 대해 스칼라로 폴백 할 수 있습니다.

ASCII 범위의 일부 문자 toupper()또는 tolower()일부 문자는 해당 범위 밖의 문자, 특히 I ↔ ı 및 İ ↔ i의 터키어를 생성하는 로케일도 있습니다 . 이러한 로케일에서는보다 정교한 검사가 필요하거나이 최적화를 전혀 사용하지 않을 것입니다.


그러나 어떤 경우에는 UTF-8 대신 ASCII를 가정 할 수 있습니다 LANG=C( 예 : POSIX 로켈이있는 유닉스 유틸리티 ) 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.