진법 변환기
숫자를 2진, 8진, 10진, 16진 및 2부터 36까지 임의 진법으로 상호 변환. 고정 폭 2의 보수와 비트 그리드 포함.
폭 8비트, 그중 8개가 켜짐
이 도구가 하는 일
같은 수를 여러 진법으로 쓸 수 있고, 프로그래머는 그중 넷 사이를 끊임없이 오갑니다: 하드웨어가 담는 것이라 2진수, 2진수의 읽을 수 있는 약기라 16진수, 사람이 그렇게 생각하니 10진수, 그리고 파일 권한과 몇몇 오래된 시스템이 아직 쓰니 8진수입니다. 여기 어느 칸에 값을 입력해도 나머지가 따라오고, 더해서 2부터 36까지 임의 진법으로 설정할 수 있는 칸도 하나 딸려 옵니다.
이것을 학교 계산기와 가르는 것은 폭 선택과 비트 그리드입니다. 8, 16, 32, 64비트를 고르면 음수가 빼기 기호가 아니라 진짜 기계 표현을 얻고, 모든 비트가 클릭할 수 있는 것이 됩니다.
진법이란 실제로 무엇인가
진법이란 다 써서 올림해야 하기까지 숫자가 몇 개 있느냐입니다. 10진수에는 열 개가 있어 9 다음이 10입니다. 2진수에는 두 개가 있어 1 다음이 10입니다. 각 자리의 위치는 진법의 거듭제곱이고, 그게 전부입니다:
1011 # 이 수를 2진수로 1 x 8 = 8 # 비트 3 0 x 4 = 0 # 비트 2 1 x 2 = 2 # 비트 1 1 x 1 = 1 # 비트 0 8+0+2+1 = 11 # 합계를 10진수로
열을 넘는 진법에는 열 개보다 많은 숫자 기호가 필요해 글자를 빌립니다: 16진수는 0-9 다음 a-f로 가고 a는 10, f는 15입니다. 그것이 진법 36까지 이어지며, 거기서는 모든 숫자와 모든 글자를 씁니다 — 맨 알파벳으로 쓸 수 있는 가장 큰 진법이자, 이 도구가 거기서 멈추는 이유입니다.
왜 16진수이고 다른 무언가가 아닌가
16진수는 하나의 특정한 이유로 인기입니다: 16은 2의 4제곱이라 정확히 16진수 한 자리가 정확히 4비트를 덮습니다. 그 덕에 16진수와 2진수의 변환이 계산 없는 찾아보기가 됩니다 — 각 16진수 자리가 주변 자리와 무관하게 저만의 4비트로 펼쳐집니다.
d e a d 1101 1110 1010 1101 # 각 16진수 자리가 저마다의 니블
8진수는 한 자리 3비트로 같은 방식으로 돕니다. 8이 2의 3제곱이기 때문입니다 — 그래서 Unix 권한이 8진수입니다: 사용자 그룹마다 세 권한 비트가 정확히 한 자리에 들어맞습니다. 10진수는 2진수와 그런 관계가 없어, 둘 사이의 변환에는 찾아보기가 아니라 진짜 나눗셈이 필요합니다.
여기 2진수 칸이 넷씩, 16진수 칸이 둘씩 묶인 것도 그래서입니다: 그 묶음이 중요한 경계에 맞으므로 세지 않고도 니블이나 바이트를 화면에서 읽어 낼 수 있습니다.
음수와 2의 보수
음수는 그 자체로는 2진 형태가 없습니다. 레지스터에 빼기 기호는 없고 — 비트뿐입니다 — 그래서 부호는 비트 자체에 부호화되어야 하고, 그러려면 비트가 몇 개인지 정해야 합니다. 폭 선택이 있는 이유이자, 바꾸면 답이 바뀌는 이유입니다.
모든 현대 기계가 쓰는 방식은 2의 보수입니다: 음수를 나타내려면 그 양수 형태를 취해 모든 비트를 뒤집고 1을 더합니다. 그 결과 맨 위 비트가 "음수"를 뜻하게 되고, 부호를 위한 특별한 경우 없이 보통의 덧셈이 계속 통합니다.
0000 0101 # 5 1111 1010 # 모든 비트 뒤집기 1111 1011 # 1 더하기: 바이트로 -5, 16진수로 fb
레지스터를 넓히면 같은 수가 다른 패턴을 얻습니다: -5는 8비트로 fb, 16비트로 fffb, 32비트로 fffffffb입니다. 값은 바뀌지 않았습니다. 그것을 나르는 비트의 수가 바뀌었습니다. 이 도구에서 폭을 바꾸면 바로 그것이 보입니다.
0xFF가 255이면서 -1이기도 한 이유
비트 ff는 그것이 부호 있는지를 말하지 않습니다. 1111 1111을 담은 바이트는 그것을 읽는 코드가 부호 없는 타입을 선언했으면 255로, 부호 있는 것을 선언했으면 -1로 읽힙니다. 바이트 자체에는 둘을 가르는 것이 아무것도 없습니다 — 타입은 그런 정보를 지니지 않은 비트에 대해 프로그램이 하는 주장입니다.
그것이 진짜 버그 한 무리의 근원입니다: 음수로 나오는 체크섬, 파일에서 읽어 0보다 작게 비교되는 바이트, 부호 유무가 구현 정의라 ARM에서 x86과 다르게 동작하는 C의 char입니다. 부호 있는 읽기와 부호 없는 읽기가 다를 때마다 이 도구는 둘 다 보여 줍니다. 그 어긋남이야말로 대개 당신이 찾던 것이기 때문입니다.
비트 그리드
현재 값의 모든 비트가 위치 번호와 함께 표시되고, 하나를 클릭하면 뒤집힙니다. 진법이 곧바로 갱신되어, 몇몇 물음을 손으로 하기보다 훨씬 쉽게 답할 수 있게 합니다:
- 이 플래그 값에서 어느 비트가 켜졌나 — 하나씩 클릭하며 위치를 읽으세요.
- 비트 4와 7의 마스크는 무엇인가 — 그 둘을 켜고 16진수를 읽으세요.
- 맨 위 비트를 켜면 부호 있는 값에 무슨 일이 생기나 — 눈에 보이게 음수가 됩니다.
- 이 값이 2의 거듭제곱인가 — 2의 거듭제곱은 정확히 한 비트만 켜져 있습니다.
비트 0이 최하위 비트이고 오른쪽에 있으며, 이는 보편적 관례이자 오른쪽에서 왼쪽으로 쓰는 페이지에서도 그리드가 왼쪽에서 오른쪽으로 남는 이유입니다. 행은 여덟 비트 폭이라 바이트 경계가 한눈에 보입니다.
큰 수도 정확하게 남는다
JavaScript 숫자는 배정밀도라 정수를 정확히 담는 것은 2^53 — 약 9천조 — 까지뿐입니다. 64비트 값은 그것을 넘을 수 있고, 보통 숫자로 만든 변환기는 그것을 조용히 반올림해 그럴듯해 보이지만 마지막 자릿수가 틀린 16진수 문자열을 냅니다.
여기서는 모든 것이 대신 임의 정밀도 정수를 씁니다. 그래서 온전한 64비트 값이 정확히 변환됩니다. 임의 정밀도 폭 모드는 더 나아가 한계를 아예 없앱니다 — 암호 값과 큰 식별자에 유용합니다 — 다만 고정 폭이 없으면 2의 보수도 없으므로, 그 모드에서 음수는 어느 진법에서든 그저 빼기 기호를 지닙니다.
2진수에서 10진수로, 10진수에서 2진수로
2진수와 10진수는 여기서 서로 아무 관계가 없는 유일한 짝입니다: 2는 10의 거듭제곱이 아니고 10도 2의 거듭제곱이 아니어서, 어느 방향도 찾아보기로 끝나지 않고 둘 다 진짜 계산이 필요합니다. 게다가 같은 계산도 아닙니다. 좀처럼 소리 내어 말하지 않는 대목인데, 한쪽 방향의 빠른 손계산은 돌아오는 방향의 빠른 손계산을 뒤집은 것이 아닙니다.
- 2진수에서 10진수로, 두 배로 하는 법: 맨 왼쪽 비트에서 0으로 시작해, 비트마다 지금까지의 값을 두 배 하고 그 비트를 더합니다. 1011이면 1, 2, 5, 11로 갑니다 — 한 번의 훑기로 끝나고 자릿값을 외울 필요가 없으며, 이 도구가 입력을 읽을 때 하는 일이 바로 이것입니다.
- 2진수에서 10진수로, 자리로 더하는 법: 켜진 비트의 자릿값을 모두 더합니다. 위의 풀이 예가 그것입니다. 켜진 비트가 두셋일 때는 빠르고, 대부분 켜져 있으면 느립니다.
- 10진수에서 2진수로, 나누는 법: 2로 나누고 나머지를 적기를 아무것도 남지 않을 때까지 되풀이한 다음, 나머지를 아래에서 위로 읽습니다. 답이 최하위 비트부터 나오기 때문에, 적는 내내 거꾸로 보입니다.
- 10진수에서 2진수로, 빼는 법: 들어가는 가장 큰 2의 거듭제곱을 빼고 되풀이합니다. 200은 128을 잃어 72가 남고, 72는 64를 잃어 8이 남고, 8은 8을 잃어 아무것도 남지 않습니다 — 그래서 위치 7, 6, 3의 비트가 켜지고 바이트는 1100 1000입니다. 켜진 비트가 적을 때는 나눗셈보다 빠릅니다.
여기 어느 손계산도 다루지 못하는 것이 하나 있습니다: 음의 10진수는 폭을 고르기 전까지 2진 형태가 아예 없습니다. 마이너스 5를 2진수로 어떻게 쓰느냐고 물으면 정직한 답은 되물음입니다 — 몇 비트인가요? 그것을 정하는 것이 폭 선택이고, 위의 2의 보수 절이 다루는 것도 그것입니다. 임의 정밀도 모드에는 폭이 없으므로 빼기 기호가 어느 진법에나 그대로 따라옵니다.
10진수에서 16진수로, 16진수에서 10진수로
이 일을 자주 하는 사람은 16으로 나누지 않습니다. 16진수와 2진수의 관계가 바로 지름길입니다: 10진수를 한 번 2진수로 바꾸고, 비트를 오른쪽부터 넷씩 자른 뒤, 각 묶음을 16진수 한 자리로 읽습니다. 돌아올 때는 같은 길을 거꾸로 가서, 16진수 각 자리를 그 4비트로 펼치고 켜진 비트의 자릿값을 더합니다. 한 바이트까지라면 더 짧은 길도 있습니다: 16진수 첫 자리는 둘째 자리의 16배이니 곱해서 더하면 됩니다.
- 255는 ff — 모든 비트가 켜진 바이트입니다. 외워 둘 값이 하나라면 이것인데, 거기서 바이트가 끝나기 때문입니다.
- 256은 100입니다. 꽉 찬 바이트에서 한 걸음 넘어가면 10진수에서 99가 100으로 넘어가는 것과 똑같이 자리가 넘어가고, 16진수는 바이트가 담을 수 없는 자리를 얻습니다.
- 65535는 ffff이고 65536은 10000 — 한 바이트 위의 같은 두 이정표이며, 16비트 계수기가 멈추는 곳입니다.
- 4096은 1000이고, 그래서 페이지 크기와 정렬과 메모리 오프셋이 16진수에서는 둥글고 10진수에서는 들쭉날쭉해 보입니다. 16진수는 비트를 넷씩 세고 하드웨어도 그렇게 셉니다.
대문자와 소문자는 구별되지 않습니다: FF와 ff는 같은 값이고, 이 도구는 입력에서 둘 다 받아들이며 출력에서는 소문자를 씁니다. 하지 않는 것은 추측입니다. 접두사 0x는 16진수 칸의 것이라 10진수 칸에서는 조용히 버려지지 않고 거부됩니다. 틀린 진법으로 읽힌 값은 변환기가 결코 그럴듯해 보이게 만들어서는 안 되는 단 하나의 잘못이기 때문입니다.
8진수에서 10진수로, 그리고 답을 바꾸는 앞자리 0
8진수가 다른 어디보다 오래 살아남은 곳은 파일 권한입니다. 사용자 그룹마다 세 권한 비트가 정확히 8진수 한 자리에 들어맞기 때문입니다. 10진수로 바꾸는 일은 8의 거듭제곱을 쓰는 평범한 자리 계산이어서, 8진수 755는 64가 일곱에 8이 다섯에 5, 즉 493입니다. 반대 방향은 8로 나누고 나머지를 아래에서 위로 읽는 것으로, 10진수에서 2진수로 갈 때와 같은 모양이고 같은 이유입니다: 8도 10과 아무 관계가 없습니다.
- 755는 10진수로 493, 644는 420, 777은 511입니다. 이 10진수들은 하나도 쓸모가 없는데 그게 요점입니다 — 권한을 8진수로 쓰는 것은 자리가 권한 비트에 맞기 때문이지 값이 무언가를 세기 때문이 아닙니다.
- 앞자리 0은 C와 Python 2에서 진법 표시입니다: 거기서 0755는 8진수, 곧 493입니다. Python 3은 그 표기를 아예 거부하고 대신 0o755를 요구하는데, 그것이 조용한 버그 한 무리를 통째로 없앴습니다.
- YAML 1.1도 따옴표 없는 0755를 493으로 읽습니다. 그래서 설정 파일의 권한 모드는 따옴표로 감싸야 하고, 그러지 않으면 적어 둔 그 모드가 아니게 됩니다.
- JSON은 처음부터 허용한 적이 없습니다. 문법이 수의 앞자리 0을 금하므로 0755는 거기서 수도 아니고 구문 오류입니다 — 셋 가운데 가장 요란하고, 유일하게 잘못 읽힐 수 없는 동작입니다.
이 도구는 앞자리 0을 무엇의 표시로도 보지 않습니다. 진법을 정하는 것은 입력하는 칸이어서, 0755는 10진수 칸에서 755이고 8진수 칸에서 493입니다. 접두사 0o는 8진수 칸에서 받아들여지고 그 밖의 어디서도 거부됩니다. 접두사로 진법을 짐작한다는 것은 입력된 것과 다른 수를 돌려준다는 뜻이기 때문입니다.
8진수에서 16진수로: 지름길이 없는 짝
둘 다 2의 거듭제곱이라 둘 다 같은 비트를 다시 묶는 일일 뿐입니다. 그런데도 이것은 네 진법 가운데 자리와 자리를 맞대는 규칙이 아예 없는 유일한 짝입니다. 8진수 한 자리는 3비트, 16진수 한 자리는 4비트이고, 셋과 넷은 서로 나누어떨어지지 않으므로 경계가 결코 맞지 않습니다. 비트를 펼쳐 쓰고 다시 묶는 일을 건너뛸 길은 없습니다.
- 8진수의 각 자리를 순서대로 그 3비트로 씁니다: 755는 111 101 101이 됩니다.
- 그 비트를 오른쪽 끝에서부터 넷씩 다시 묶고, 개수가 딱 나누어떨어지지 않으면 왼쪽을 0으로 채웁니다: 0001 1110 1101.
- 넷씩 묶인 것을 16진수 한 자리로 읽습니다: 1ed. 채우는 끝을 잘못 고르는 것이 여기서 흔한 실수인데, 틀려 보이지도 않습니다 — 그저 조용히 답을 몇 배로 만들 뿐입니다.
16진수 두 자리가 정확히 한 바이트이고 8진수 두 자리는 그렇지 않다는 것이, 메모리를 읽는 자리에서 16진수가 8진수를 밀어낸 이유의 전부입니다: 8비트는 8진수 두 자리와 3분의 2여서 바이트 경계가 자리 한가운데 떨어집니다. 8진수는 워드 크기가 3비트의 배수였던 기계에 맞았고, 16진수는 8비트 바이트에 맞습니다. 그리고 이 페이지에서는 이 짝에 한해 비트 그리드가 어느 손계산보다 빠릅니다 — 비트를 한 번 켜면 두 칸이 이미 답을 보여 줍니다.
자주 묻는 질문
- -5가 2진수로 -101이 아니라 fb로 표시되는 이유는 무엇인가요?
- 레지스터에 빼기 기호가 없기 때문입니다. 폭을 고르면 음수는 2의 보수로 표시되고, 이는 기계가 실제로 저장하는 것입니다: 바이트로 -5는 1111 1011, 즉 fb입니다. 부호 있는 수학적 형태를 원하면 폭을 임의 정밀도로 바꾸세요.
- 0xFF는 255인가요, -1인가요?
- 둘 다입니다 — 비트는 동일하고 선언된 타입만이 정합니다. 부호 있는 8비트 값은 ff를 -1로, 부호 없는 것은 255로 읽습니다. 두 읽기가 다를 때마다 이 도구는 그것들을 나란히 보여 줍니다.
- 10진수 대신 16진수가 그토록 많이 쓰이는 이유는 무엇인가요?
- 16진수 한 자리가 정확히 4비트라, 16진수와 2진수가 계산 없는 찾아보기로 변환되고 자리가 바이트 경계에 맞기 때문입니다. 10진수는 2진수와 그런 관계가 없어, 10진수 값은 어느 비트가 켜졌는지 아무것도 말해 주지 않습니다.
- 여기서 가장 높은 진법은 무엇이고 왜 36인가요?
- 36입니다. 숫자 10개에 글자 26개 — 맨 라틴 알파벳이 주는 모든 기호 — 이기 때문입니다. 더 높이 가려면 어떤 추가 문자를 쓸지에 대한 합의가 필요한데, 합의된 하나가 없습니다.
- 0x나 공백이 든 값을 붙여넣을 수 있나요?
- 네. 0x, 0b, 0o 접두사는 맞는 진법에서 받아들여지고, 공백과 밑줄은 무시되므로, 코드나 데이터시트에서 그대로 붙여넣어도 먼저 정리할 필요가 없습니다.
- 64비트 값이 정확히 변환되나요?
- 네. 모든 연산이 임의 정밀도 정수를 쓰므로, 2^53 — 보통 JavaScript 숫자가 반올림하기 시작하는 곳 — 을 넘은 값도 정확히 남습니다.
- 제가 입력한 것이 서버로 전송되나요?
- 아니요. 브라우저 안의 연산입니다. 아무것도 업로드되거나 기록되지 않고, 네트워크 연결 없이 동작합니다.
- 2진수를 손으로 10진수로 바꾸려면 어떻게 하나요?
- 왼쪽부터 두 배 하고 더합니다: 0에서 시작해 비트마다 지금까지의 값을 두 배 하고 그 비트를 더하세요. 1011은 1, 다음 2, 다음 5, 다음 11로 갑니다. 한 번의 훑기로 끝나고 자릿값을 외울 필요가 없으며, 같은 방법이 어느 진법에서나 통합니다 — 두 배 대신 진법을 곱하면 됩니다.
- 255는 16진수로 얼마이고, 왜 이 수가 자꾸 나오나요?
- ff입니다. 여덟 비트가 모두 켜진 바이트라 한 바이트가 담는 가장 큰 값이고, 셈이 둘째 바이트로 넘어가는 지점입니다 — 그래서 색 채널과 마스크와 크기 한계 곳곳에 나옵니다. 그보다 하나 큰 256은 16진수로 100이라고 씁니다.
- 0755는 755와 같은 수인가요?
- 여기서는 10진수 칸에서는 같고 8진수 칸에서는 다릅니다: 진법을 정하는 것은 칸이고 앞자리 0은 그저 0이라는 자리입니다. 그 밖에서는 언어에 따라 다릅니다 — C와 Python 2는 0755를 8진수, 곧 493으로 읽고, Python 3은 그 표기를 거부하며 0o755를 요구하고, YAML 1.1은 493으로 읽으며, JSON은 구문 오류로 다룹니다.
- 8진수를 16진수로 어떻게 바꾸나요?
- 2진수를 거칩니다. 더 짧은 길이 없기 때문입니다: 8진수 한 자리에 3비트, 16진수 한 자리에 4비트라 둘은 결코 맞지 않습니다. 8진수의 각 자리를 그 3비트로 펼치고, 전체를 오른쪽부터 넷씩 다시 묶고, 넷씩을 16진수 한 자리로 읽으세요 — 755는 111 101 101이 되고, 다음은 0001 1110 1101, 그다음이 1ed입니다.