코드 속 매직 넘버 읽기
& 0xFF, >>> 24, | (1 << 3), 0o644 같은 조각이 어느 비트를 꺼내고 어느 비트를 켜는지, 11–15단계를 마치면 한눈에 보입니다.
Guide
진법과 비트 연산 강좌는 17단계 인터랙티브 튜토리얼로, 「2진수」를 추상 개념에서 손으로 만질 수 있는 스위치로 바꿔 줍니다. 수를 입력하면 2·8·10·16진수 네 가지 표기가 함께 바뀌고, 비트 하나를 켜거나 끄면 값이 즉시 갱신됩니다. 두 수의 AND / OR / XOR / 시프트 결과는 비트끼리 자리를 맞춰 보여 줍니다. 소수를 입력하면 부호, 지수, 가수 세 필드로 쪼개지고 그것이 배정밀도에서 정확히 같은 십진 소수는 무엇인지 확인할 수 있습니다. 단계마다 목표가 하나씩 있습니다. 어떤 수를 변환하기, 200 만들기, -5의 2의 보수 쓰기, 5 << 3 계산하기 같은 목표를 판정기가 즉시 채점합니다.
2026-09-09 업데이트4 sources11 min read
진법과 비트 연산 강좌는 17단계 인터랙티브 튜토리얼로, 「2진수」를 추상 개념에서 손으로 만질 수 있는 스위치로 바꿔 줍니다. 수를 입력하면 2·8·10·16진수 네 가지 표기가 함께 바뀌고, 비트 하나를 켜거나 끄면 값이 즉시 갱신됩니다. 두 수의 AND / OR / XOR / 시프트 결과는 비트끼리 자리를 맞춰 보여 줍니다. 소수를 입력하면 부호, 지수, 가수 세 필드로 쪼개지고 그것이 배정밀도에서 정확히 같은 십진 소수는 무엇인지 확인할 수 있습니다. 단계마다 목표가 하나씩 있습니다. 어떤 수를 변환하기, 200 만들기, -5의 2의 보수 쓰기, 5 << 3 계산하기 같은 목표를 판정기가 즉시 채점합니다.
강좌는 네 구간으로 나뉩니다. 진법 변환(2 ↔ 10진수, 2의 거듭제곱, 16진수와 8진수가 왜 「속기」인지), 부호 있는 수(2의 보수가 어떻게 뺄셈을 덧셈으로 바꾸는지, 오버플로가 왜 127 + 1을 -128로 만드는지), 비트 연산(AND 마스크, OR 설정, XOR 뒤집기, 시프트 곱셈·나눗셈, 그리고 Linux 권한 비트 755에 그대로 적용되는 예), 부동소수점(IEEE 754의 필드 배치와 0.1 + 0.2 !== 0.3의 진짜 이유)입니다.
약 45분이면 끝납니다. 프로그래밍을 처음 배우는 사람, 면접을 준비하는 개발자, 0x1F, & 0xFF, chmod 644를 볼 때마다 검색하는 사람에게 맞습니다. 수학 기초는 필요 없고 덧셈만 할 줄 알면 됩니다.
0x를 붙여도 됩니다.?lesson=n으로 특정 단계에 바로 들어갈 수 있습니다.이 페이지 위쪽이 곧 강좌 본체입니다. 값을 바꾸고, 비트를 누르고, 답을 채워 보세요.
9단계 「2의 보수 구하기」에서는 -5의 8비트 2의 보수를 써야 합니다.
16단계 「IEEE 754 필드」에 0.15625를 입력하면 패널이 이렇게 보입니다.
절댓값 5 0 0 0 0 0 1 0 1
비트 반전 1 1 1 1 1 0 1 0
1 더하기 1 1 1 1 1 0 1 1 ← 답 11111011
검증 부호 없음 251, 부호 있음 251 − 256 = −5 ✓0.15625 = 0.00101₂ = 1.01₂ × 2⁻³
부호 0
지수 −3 + 127 = 124 = 01111100 ← 답 124
가수 01000000000000000000000(암시적 선행 1)
32비트 0 01111100 01000000000000000000000 = 0x3E200000모든 진법은 「자릿값의 합」입니다. 10진수 203 = 2×100 + 0×10 + 3×1이고, 2진수 1011 = 1×8 + 0×4 + 1×2 + 1×1 = 11입니다. 오른쪽에서 왼쪽으로 자릿값은 1, 2, 4, 8, 16, 32, 64, 128… 순서입니다. 2진수를 10진수로 바꾸는 일은 1인 자릿값을 더하는 것이고, 10진수를 2진수로 바꾸는 일은 2로 계속 나누며 나머지를 적거나 큰 2의 거듭제곱부터 탐욕적으로 빼는 것입니다(7단계의 비트 스위치가 바로 이 과정을 시각화한 것입니다).
는 1000과 2.4%밖에 차이가 나지 않아 초기에 KB를 1024바이트로 썼습니다. 그 결과 지금까지 혼란이 남았습니다. 저장장치 제조사는 기준으로 1 GB를 표기하고 운영체제는 으로 계산해, 500 GB 디스크가 465 GiB로 표시됩니다. IEC는 이 때문에 KiB, MiB, GiB(2진 접두사)를 정의했고 Linux와 macOS는 차차 받아들였지만, Windows는 여전히 GB로 표시하면서 GiB로 계산합니다. 몇 가지 수를 외워 두면 편합니다. , , , .
2진수는 너무 길어 사람이 읽기 어렵습니다. 16진수(0–9에 A–F를 더함)는 기호 하나로 4비트를 나타내므로 1바이트가 정확히 두 자리입니다. 11111111 = FF, 10110110 = B6입니다. 변환에 10진수를 거칠 필요가 없습니다. 2진수를 오른쪽부터 4비트씩 끊어 표에서 찾으면 되고, 5단계가 이 연습입니다. 8진수는 기호 하나로 3비트를 나타내며 역사적으로 12·24·36비트 머신에 쓰였고, 오늘날에는 주로 Linux 권한(755)과 C의 \012 이스케이프에 살아 있습니다. 프로그램에서 쓰는 접두사는 0x가 16진수, 0o가 8진수, 0b가 2진수입니다.
음이 아닌 정수는 2진수 표기가 하나뿐이라, 8비트 수를 서로 간섭하지 않는 스위치 8개(플래그 비트)로 쓸 수 있습니다. flags = READ | WRITE, if (flags & EXEC) 같은 식입니다. CSS의 font-weight, Unix 파일 권한, 네트워크 프로토콜 헤더의 플래그 필드가 모두 이렇게 묶여 있습니다. 7단계에서는 스위치만 눌러 200을 만들어야 합니다. 가장 높은 자리부터 탐욕적으로 가면 200 ≥ 128이므로 켜고, 남은 72 ≥ 64이므로 켜고, 남은 8 = 2³이므로 켜서 11001000이 됩니다.
음수는 어떻게 저장할까요? 직관적으로는 한 비트를 부호로 쓰고 싶지만 그러면 「+0」과 「−0」 두 개의 0이 생기고 덧셈 회로도 경우를 나눠야 합니다. 2의 보수(two's complement)의 규칙은 최상위 비트가 1이면 음수이고, 값은 부호 없는 값에서 을 뺀 것과 같다는 것입니다. 8비트에서 11111111은 255이면서 255 − 256 = −1이고 10000000은 −128입니다. 음수의 2의 보수를 구하려면 절댓값을 쓰고, 비트를 반전하고, 1을 더합니다. −5는 00000101 → 11111010 → 11111011입니다.
2의 보수의 묘미는 덧셈이 부호를 알 필요가 없다는 점입니다. −5 + 5 = 11111011 + 00000101 = 1 00000000이고 9번째 비트의 자리올림을 버리면 정확히 0입니다. 그래서 뺄셈은 「2의 보수를 더하기」가 되고 CPU에는 덧셈기 하나만 있으면 됩니다. 8비트 2의 보수 범위는 −128에서 127까지입니다. 음수가 양수보다 하나 많은데, 0이 양수 쪽 자리를 차지하기 때문입니다.
비트 폭이 유한하므로 넘치면 되돌아갑니다. int8에서 127 = 01111111에 1을 더하면 10000000이 되고, 2의 보수로 해석하면 −128입니다. C, Java, Go의 정수 오버플로가 모두 이렇게 조용히 일어납니다. JavaScript의 일반 Number는 부동소수점이라 오버플로가 없지만, Int8Array, |0, 모든 비트 연산은 32비트 2의 보수 위에서 동작하므로 (2 ** 31) | 0은 −2147483648이 됩니다. 유명한 2038년 문제는 32비트 부호 있는 초 값이 2038-01-19 03:14:07 UTC에 음수로 넘치는 것이고, 옛 게임의 「점수가 음수로 바뀜」이나 「골드가 0이 됨」도 같은 원리입니다.
세 가지 비트 논리 연산에는 각각 정해진 용도가 있습니다.
| 연산 | 규칙 | 관용적 용법 |
|---|---|---|
AND & |
두 비트가 모두 1일 때만 1 | 마스크로 비트 추출: x & 0x0F는 하위 4비트, IP & 서브넷 마스크는 네트워크 주소, n & 1은 홀짝 판정 |
OR | |
한 비트라도 1이면 1 | 비트 설정: flags | READ는 다른 플래그를 건드리지 않고 하나만 켬 |
XOR ^ |
두 비트가 다를 때만 1 | 뒤집기 / 복원: x ^ m ^ m = x, 일회용 패드, 체크섬, 임시 변수 없는 교환 |
「끄기」는 AND와 NOT의 조합입니다. flags & ~FLAG처럼 씁니다. XOR의 두 항등식 , 가 모든 용도의 뿌리입니다. a ^= b; b ^= a; a ^= b로 두 수를 교환하고, 배열에서 홀수 번만 나타나는 원소를 찾을 때는 모든 수를 XOR하면 됩니다.
왼쪽으로 n비트 시프트하는 것은 을 곱하는 것과 같고(낮은 자리는 0으로 채움), 오른쪽 시프트는 으로 나눈 뒤 내림한 것과 같습니다. 5 << 3 = 101000 = 40입니다. 시프트는 가장 싼 곱셈·나눗셈이고, 더 흔한 용도는 작은 필드를 한 정수에 묶는 것입니다. (r << 16) | (g << 8) | b는 0–255 값 셋을 24비트 색상 값으로 만들고, 반대로 (rgb >> 8) & 0xFF는 G를 꺼냅니다. 오른쪽 시프트에는 두 가지가 있습니다. 산술 시프트 >>는 부호 비트를 유지하고(음수는 음수로 남음), 논리 시프트 >>>는 0을 채웁니다. JavaScript의 시프트는 32비트에서 이뤄지며 1 << 31은 −2147483648이 되고 1 << 32는 1 << 0 = 1이 됩니다. 시프트 횟수를 32로 나눈 나머지로 처리하기 때문입니다.
Linux 파일 권한은 9개 비트입니다. 소유자, 그룹, 기타 사용자마다 3비트가 배정되고 읽기 r = 4, 쓰기 w = 2, 실행 x = 1 순서입니다. 세 비트를 더하면 8진수 한 자리가 됩니다. rwxr-xr-x = 7, 5, 5 → 755이고 rw-r--r-- = 644입니다. 자릿값과 마스크가 일상 운영에서 가장 직접 쓰이는 예입니다. chmod u+x는 0o100을 OR하는 것이고 chmod o-w는 ~0o002를 AND하는 것입니다. 앞에 10–12번째 비트(setuid, setgid, sticky)가 붙어 네 자리 4755를 보게 되는 경우도 있습니다.
부동소수점은 2진수로 쓴 과학적 표기법입니다.
단정도 32비트 = 부호 1비트 s + 지수 8비트 e(바이어스 127) + 가수 23비트 f이고, 배정밀도 64비트 = 1 + 11(바이어스 1023) + 52입니다. 가수에는 소수부만 저장하고 정수부의 1은 암시적으로 두어 정밀도 한 자리를 공짜로 얻습니다. 지수에 바이어스를 더해 저장하는 이유는 부동소수점을 비트 단위로 비교할 때 정수처럼 단조로워지게 하기 위해서입니다. 0.15625 = = 이므로 지수 필드는 −3 + 127 = 124 = 01111100이고, 가수 필드는 01 뒤에 0을 채운 형태입니다.
특수한 인코딩도 몇 가지 있습니다. 지수가 전부 0이면 비정규화 수(0에 아주 가까운 값과 ±0을 표현), 지수가 전부 1이고 가수가 전부 0이면 ±∞, 지수가 전부 1이고 가수가 0이 아니면 NaN입니다. 0/0이 NaN이고 1/0이 Infinity인 이유가 여기 있으며, NaN이 여러 비트 패턴을 가지면서도 모두 「자기 자신과 같지 않은」 이유도 여기 있습니다.
2진 소수는 2의 음의 거듭제곱 합, 즉 0.5, 0.25, 0.125…만 정확히 표현할 수 있습니다. 은 2진수에서 무한 순환소수 이라 52비트 가수에서 반올림해야 합니다. 그래서 배정밀도의 0.1은
와 정확히 같으며 0.1보다 아주 조금 큽니다. 0.2도 마찬가지로 조금 크고, 둘을 더해 반올림하면 0.3000000000000000444…가 됩니다. 반면 리터럴 0.3에 가장 가까운 배정밀도 값은 0.29999999999999998889…입니다. 서로 다른 두 2진수이므로 0.1 + 0.2 === 0.3은 false입니다. JavaScript의 버그가 아니라 IEEE 754를 쓰는 모든 언어(Python, Java, C, Go)가 똑같습니다.
대응은 상황에 따라 다릅니다. 돈에는 정수 「전」 단위나 십진 라이브러리(decimal, BigDecimal, Decimal.js)를 씁니다. 비교에는 오차 범위 Math.abs(a − b) < ε를 쓰고, 표시에는 toFixed(2)를 마지막 한 번만 적용하며, 개수 세기에는 정수를 씁니다. 배정밀도는 이내의 모든 정수를 정확히 표현하므로 그보다 커지면 BigInt를 써야 합니다. 강좌 마지막 단계의 선택지 「저장된 0.1이 참값보다 큰가 작은가」의 답은 「크다」입니다. 정확한 전개에서 18번째 자리 뒤의 55511…을 보면 알 수 있습니다.
BigInt의 비트 연산은 다루지 않습니다.Math.fround, 반정밀도와 bfloat16은 다루지 않습니다.0x를 붙여도 됩니다.10000000은 −0이 아니라 −128이고, −1은 전부 1입니다.>>는 그냥 2로 나누기」: 음수에서는 0 방향이 아니라 아래 방향으로 내림합니다. −5 >> 1은 −3이고 Math.trunc(−5 / 2)는 −2입니다.toFixed를 쓰면 금액 문제가 해결된다」: toFixed는 표시용 반올림일 뿐이고 중간 연산의 오차는 이미 쌓였습니다. 금액은 처음부터 정수 전 단위로 다뤄야 합니다.Int32Array, | 0은 모두 32비트 2의 보수 위에서 돌아가므로 오버플로가 있습니다. Number도 을 넘으면 정수 정밀도를 잃습니다.& 0xFF, >>> 24, | (1 << 3), 0o644 같은 조각이 어느 비트를 꺼내고 어느 비트를 켜는지, 11–15단계를 마치면 한눈에 보입니다.
10단계의 오버플로와 17단계의 반올림이 이런 버그 대부분을 설명합니다. 앞의 문제는 더 넓은 타입이나 BigInt로, 뒤의 문제는 정수 전 단위나 십진 라이브러리로 해결합니다.
서브넷 마스크는 AND 마스크입니다(11단계). 192.168.1.37 & 255.255.255.0 = 192.168.1.0이고, chmod 755 / 644의 의미와 「스크립트에 왜 실행 권한이 없을까」도 15단계에서 다룹니다.
2의 보수, 오버플로, XOR 교환, 시프트 곱셈·나눗셈, IEEE 754 필드, 0.1 + 0.2는 가장 자주 나오는 기초 질문이고, 단계마다의 판정이 곧 자가 테스트가 됩니다.
대부분의 단계는 앞의 0을 생략해도 됩니다(1011과 00001011 모두 정답). 「8비트 2의 보수」라고 밝힌 단계는 정확히 8자리를 요구하는데, 비트 폭 자체가 답의 일부이기 때문입니다.
0x는 붙여야 하나요?상관없습니다. ff, FF, 0xFF 모두 정답입니다.
같은 비트열을 다르게 읽는 두 방식입니다. 부호 없음은 최상위 비트를 로, 부호 있음은 로 봅니다. 11111111은 8비트에서 부호 없음이면 255, 부호 있음이면 −1입니다.
배정밀도의 가수와 지수를 정의대로 유리수로 전개한 뒤 긴 나눗셈을 해서 십진 자릿수를 모두 구합니다. 근사가 아니라 그 2진수의 정확한 십진 표현입니다.
현재 브라우저의 localStorage에 저장되고 업로드되지 않습니다. 「이 단계 공유」 링크에는 단계 번호만 들어가고 진행 상황은 들어가지 않습니다.
강좌 전부가 브라우저 로컬에서 돕니다. 진법 변환, 비트 연산, 부동소수점 필드 분해와 정확 전개는 모두 페이지 안의 순수 함수 계산이라 서버를 부르지 않습니다. 진행 상황은 이 기기의 localStorage에 저장됩니다.
chmod 도구 — 기호 모드와 8진 모드의 정의: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/chmod.html(열람일: 2026-09-09)2026-09-09 업데이트
17단계: 2/8/16진수 변환, 비트 토글, 2의 보수와 오버플로, AND/OR/XOR/시프트, chmod 권한 비트, IEEE 754 구조와 0.1+0.2의 비밀
이 도구는 아직 완전히 번역되지 않아 일부 내용이 원문 또는 대체 언어로 표시됩니다.
목표값 패널에는 이진수 1011이 표시됩니다. 십진수로 무엇입니까?
각 숫자의 자릿값은 2의 거듭제곱입니다. 가장 오른쪽은 2⁰=1이고 그 다음에는 2, 4, 8, 16… 비트가 1인 가중치를 추가합니다: 1011 = 8 + 0 + 2 + 1 = 11. 패널에서 임의의 비트를 클릭하면 즉시 소수점이 업데이트됩니다. 이는 규칙을 암기하는 것보다 직관력을 키우는 더 빠른 방법입니다.