1장 : 컴퓨터 내부의 언어 체계
컴퓨터는 어떤 말을 사용할까?
Last updated
컴퓨터는 어떤 말을 사용할까?
Last updated
1장에서는 컴퓨터의 언어를 배우기 시작한다.
언어는 정보를 서로 소통하기 위해 만들어졌는데 프로그래머가 해야 할 일은 컴퓨터에게 명령을 내리는 것이다.
그런데 컴퓨터는 사람의 말을 알아듣지 못한다.
따라서 사람이 컴퓨터의 말을 배워야 한다. 자연어와 컴퓨터 언어는 쓰인 기호나 제대로 된 기호 나열 방법, 사용법 등의 요소를 상당 부분 공유한다. 자연어가비문자 언어라는 것과 달리 컴퓨터 언어는 항상 문자 언어만 사용한다.
언어는 편의를 제공하기 위한 지름길이다.
모든 언어의 뜻은 기호의 집합으로 인코딩된다. 하지만 의미를 기호로 인코딩하는 것만으로는 충분하지 않다.
언어가 제대로 작동하려면 의사소통하는 당사자들이 모두 같은 문맥을 공유해서 같은 기호에 같은 뜻을 부여할 수 있어야 한다.
동일한 기호를 구분할 수 있는 요소는 문맥뿐인데, 언제나 문맥을 명확히 식별할 수 있는 것은 아니다. 컴퓨터 언어에도 마찬가지 문제가 있다.
문자 언어는 기호를 나열한 것이다. 기호를 정해진 순서대로 단어를만들 수 있다.
기호나 조합은 무궁무진한 가능성이 있다. 언어마다 기호와 기호 유형이 달라질 수 있고, 언어마다 순서도 다를 수 있다.
기호의 순서도 중요하다. ‘dog’와 ‘god’는 같지 않다.
기호가 들어갈 상자
상자에 들어갈 기호
상자의 순서
일부 언어에는 주변의 상자 안에 들어 있는 기호의 종류에 따라 상자에 들어갈 수 있는 기호를 제한하는 복잡한 규칙이 존재하기도 한다.
예를 들어, 어떤 기호들은 서로 인접한 상자에 들어 갈 수 없다.
먼저 상자에 대해 생각해보자.
자연어에서는 이 상자를 문자라고 부르고, 컴퓨터에서는 비트라고 부른다.
비트 = 바이너리(binary) + 디지트(digit)
바이너리 : 2진법 사용한다는 뜻으로, 두 가지 부분으로 이뤄진 어떤 대상을 뜻한다.
디지트 : 숫자를 뜻하며, 일상생활에서 쓰는 10진수를 표현 하는 10가지 기호(0~9)를 뜻한다.
기묘한 바이너리와 디지트의 결합인 비트로, 비트는 적은 비용으로 편리하게 기호를 담을 수 있음
비트는 2진법을 사용한다.
비트 상자에는 모스 부호의 점(.)과 선(-)처럼 두 가지 기호 중 하나만 담을 수 있다는 뜻
모스 부호는 두 기호를 여러 가지 방식으로 조합해 이어붙여서 복잡한 정보를 표현한다.
예를 들어 A는 점-선이고, B는 선-점-점-점, C는 선-점-선-점이다. 자연어와 마찬가지로 기호의 순서가 중요하다.
선-점은 A가 아니라 N이다.
기호라는 개념은 추상적이다. 실제로는 기호가 무엇이든 관계없다.
하지만 가장 중요한 것은 언어는 문맥 없이는 제대로 작동할 수 없다.
기호 자체는 무엇이든 될 수 있으며, 특정한 형태를 가질 필요가 없다.
예를 들어, 꺼짐/켜짐, 낮/밤, 쌀/보리 등 다양한 방식으로 표현될 수 있다.
언어는 문맥이 있어야 제대로 작동한다.
같은 기호라도 말하는 사람과 듣는 사람이 다르게 해석하면 혼란이 발생할 수 있음.
예: "U(점-점-선)"을 말하는 사람이 의도했지만, 상대가 "쌀-쌀-보리"로 해석하면 의미가 어긋남.
컴퓨터에서 계산을 수행하는 방식을 컴퓨팅이라 부름.
비트 자체는 의미가 없으며, 필요에 따라 의미를 부여하는 방식(척하기)이 중요.
예를 들어, "이 비트들은 파란색을 뜻한다고 가정하자"처럼 의미를 임의로 정할 수 있음.
표준적인 비트 사용법을 배우는 것도 중요하지만, 필요에 따라 자신만의 방식으로 비트를 활용하는 것을 두려워할 필요 없음.
프로그래밍은 본질적으로 비트에 특정 의미를 부여하는 과정이다.
컴퓨터에서 사용되는 기호와 비트는 본질적으로 문맥에 따라 의미가 달라지며, 필요에 따라 유연하게 해석될 수 있다. 프로그래밍은 이런 유연성을 바탕으로 다양한 표현을 가능하게 한다.
비트 사용법 중 하나는 “날씨가 추운가?”, “내 모자를 좋아하나?”와 같은 예/아니요 질문에 대 한 답을 표현하는 것이다.
이때 ‘예’를 참true이라는 용어로 부르고, ‘아니요’를 거짓false이라고 부른다.
다른 비트들이 표현하는 내용으로부터 새로운 비트를 만들어내는 이런 동작을 논리 연산logic operation이라고 한다.
일반 대수와 마찬가지로 결합 법칙, 교환 법칙, 분배 법칙을 불리언 대수에 적용할 수 있다.
입력 input
은 박스의 밖에 있고, 출력output
은 박스 안에 있다.
NOT: ‘논리적 반대’를 의미한다. 예를 들어, 거짓인 비트에 NOT을 하면 참이 되고 참인 비트에 NOT을 하면 거짓이 된다.
AND: 둘 이상의 비트에 작용한다. 2비트 연산인 경우 첫 번째 비트가 참이고 두 번째 비트도 참인 경우에만 결과가 참이 된다. 2비트보다 더 많은 비트에 연산을 적용하는 경우 모든 비트가 참이면 AND 연산의 결과도 참이다.
OR: 둘 이상의 비트에 작용한다. 2비트 연산인 경우 첫 번째 비트가 참이거나 두 번째 비트가 참이면 결과가 참이 된다. 2비트보다 더 많은 비트에 연산을 적용하는 경우 어느 한 비트라도 참이면 OR 연산의 결과도 참이다.
XOR: 이 배타적(exclusive OR)의 결과는 첫 번째 비트와 두 번째 비트가 다른 값인 경우에만 참이 된다. 즉, 두 값 중 어느 하나가 참이면 XOR도 참이지만 두 값이 모두 참이면 XOR의 결과는 거짓이다.
다른 연산을 사용해 XOR 연산을 만들 수 있다. 예를 들어, 두 비트에 대한 XOR 연산(a XOR b)은 (a OR b) AND (NOT(a AND b))와 같다. 이를 통해 기본 불리언 연산을 다양한 방식으로 조합해 똑같은 결과를 얻을 수 있다는 사실을 알 수 있다.
오거스터스 드모르간은 불리언 대수에 적용할 수 있는 법칙을 추가로 알아냈다. 발명자의 이름을 따서 드모르간의 법칙
이라고 한다.
a AND b = NOT(NOT a OR NOT b)
NOT
을 충분히 사용하면 AND 연산을 OR 연산
으로 대신할 수 있다(그리고 역으로 OR을 AND로 대신할 수 있다)는 뜻이다.
컴퓨터에서 입력을 항상 원하는 형태로 얻을 수만은 없기 때문에 이런 성질이 유용할 때가 있다. 자연어에서 이중 부정이 이와 비슷한 일을 한다.
지금까지 살펴본 긍정적인 논리positive logic에 더해 부정적인 논리negative logic를 기술하는 명제를 사용할 때
드모르간의 법칙
을 활용할 수 있다.
부정논리도 작동하나. 연산을 최소로 사용하면 비용을 최소화할 수 있다. 여기서는 NOT 연산을 수행하는 하드웨어에 실제로 돈이 들기도 하고, 다음 장에서 보겠지만 연산을 연쇄적으로 사용하면 계산이 느려진다.
드모르간의 법칙을 알면 두 번째 표가 표현하는 논리가 ‘춥다 OR 비가 온다’와 동등함을 알 수 있고, 훨씬 간단하게 결과를 계산할 수 있다.
비트를 사용해 수를 표현하는 방법을 알아보자. 수는 논리보다 더 복잡하지만 단어보다는 훨씬 단순하다.
우리는 손가락과 발가락이 10개이기 때문에 10진수를 사용함.
10가지 숫자(0~9)를 사용하며, 숫자는 오른쪽에서 왼쪽으로 자리값이 증가함.
각 자리값은 10의 거듭제곱(1, 10, 100, 1000 등)으로 표현됨.
예: 5,028 = 5×10³ + 0×10² + 2×10¹ + 8×10⁰
각 상자에 사용할 수 있는 기호는 1과 0 두 가지밖에 없다. 하지만 기호가 2개뿐이라고 문제가 되지는 않는다. 10진수에서 수를 표현할 수 없을 때는 더 큰 수를 표현하기 위해 상자 를 추가할 수 있었다
2진수는 0과 1만 사용하며, 자리값은 2의 거듭제곱(1, 2, 4, 8, 16, ...)을 기반으로 함.
10진수에서 자릿수를 늘려서 더 큰 수를 표현하는 것처럼, 2진수에서도 비트(자리)를 추가하면 더 큰 수를 표현할 수 있음.
1보다 큰 수를 표현하려면 새로 상자를 추가해야 함
예: 5,028을 2진수로 변환
5,028 = 1×2¹² + 0×2¹¹ + 0×2¹⁰ + 1×2⁹ + 1×2⁸ + 1×2⁷ + 0×2⁶ + 1×2⁵ + 0×2⁴ + 0×2³ + 1×2² + 0×2¹ + 0×2⁰
결과: 1001110100100₂
비트 개수가 많을수록 표현할 수 있는 값의 범위가 커짐. 표현할 수 있는 수의 범위가 커진다.
예:
4비트 → 0~15 (16개)
8비트 → 0~255 (256개)
16비트 → 0~65,535 (65,536개)
32비트 → 0~4,294,967,295 (약 43억 개)
64비트 → 0~18,446,744,073,709,551,615 (약 18경 개)
LSB (Least Significant Bit): 2진수에서 가장 오른쪽 비트 (값 변화가 가장 작음).
MSB (Most Significant Bit): 2진수에서 가장 왼쪽 비트 (값 변화가 가장 큼).
예: 5,028(1001110100100₂)에서 MSB는 맨 왼쪽 1, LSB는 맨 오른쪽 0
2진수에서 더 많은 비트를 사용할 경우, 앞쪽에 0을 추가해도 값은 변하지 않음.
10진수와 마찬가지로 가장 왼쪽에 있는 상자보다 더 왼쪽에 영(0)을 추가하면(이 런 식으로 추가된 영들을 리딩 제로leading zero라고 말한다)
어떤 값을 표현하는 데 필요한 최소한의 상자 개수보다 더 많은 상자를 추가할 수 있다.
예: 5,028을 16비트로 표현하면 0001001110100100
10진수에서도 5,028과 05,028은 같은 값
컴퓨터가 미리 정해진 수의 비트를 한 덩어리로 사용하도록 만들어졌기 때문에, 2진수를 쓸 때는 이런 식으로 항상 일정한 개수의 비트를 사용해 값을 표현하는 경우가 종종 있다.
10진 덧셈에서 오른쪽에서 왼쪽으로 각 자리의 숫자를 서로 더한 것처럼 마찬가지로, 2진수에서도 각 비트를 LSB에서 MSB 쪽으로 더하며 결과가 1보다 크면 1을 다음 자리(왼쪽)로 올린다.
이 글은 2진수 덧셈의 원리, 논리 연산을 활용한 계산 방식, 오버플로와 언더플로 개념을 설명한다.
10진수 덧셈: 오른쪽(Lowest Significant Bit, LSB)부터 더하며, 9를 초과하면 1을 올림.
2진수 덧셈: 1+1은 2이지만, 2진수에서는 2가 없으므로 10(1 올림, 0 유지)로 변환.
예) 1(001₂) + 5(101₂) = 6(110₂)
LSB(맨 오른쪽): 1+1 = 10 → 0을 남기고 1을 올림.
중간 자리: 0+0에 올림(1) 추가 → 1.
MSB(맨 왼쪽): 0+1 = 1.
최종 결과: 110₂ (10진수 6과 동일함).
XOR(배타적 논리합, Exclusive OR): 각 비트를 더한 결과를 결정.
0+0 = 0, 1+0 = 1, 0+1 = 1, 1+1 = 0 (올림 발생).
AND(논리곱, AND): 올림을 결정.
0 AND 0 = 0, 1 AND 0 = 0, 0 AND 1 = 0, 1 AND 1 = 1 (올림 발생).
예)
1 + 1 → AND(1), XOR(0) → 올림
1 + 0 → AND(0), XOR(1) → 올림 없음
3비트 이상 덧셈에서는 2비트 덧셈을 두 번 수행하면 됨.
오버플로: 덧셈 결과가 사용 가능한 비트 범위를 초과하는 경우.
예) 4비트로 9(1001₂) + 8(1000₂) = 17(10001₂) → MSB를 초과한 1이 손실됨(결과: 0001₂, 즉 1).
컴퓨터는 오버플로 비트를 사용하여 이런 문제를 감지.
언더플로: 뺄셈 시 MSB 위쪽에서 1을 빌려와야 하는 경우.
컴퓨터에는 조건 코드(상태 코드) 레지스터가 있어 몇 가지 이상한 정보를 담아둔다.
이런 정보 중에는 오버플로 비트overflow bit가 있고, 이 비트에는 MSB에서 발생 한 올림값이 들어간다.
이 비트값을 보면 오버플로가 발생했는지 알 수 있다
이에 해당하는 조건 코드도 컴퓨터에 들어 있다.
뺄셈은 사실상 덧셈으로 변환 가능 (예: 7 - 5 = 7 + (-5)).
음수를 표현하는 방법(2의 보수 등)을 설명할 예정.
2진수 덧셈은 XOR로 결과, AND로 올림을 계산하면 된다.
오버플로와 언더플로는 비트 범위를 벗어날 때 발생하며, 컴퓨터는 이를 감지하는 조건 코드를 사용한다.
음수 표현과 뺄셈은 덧셈으로 변환 가능하다.
오버플로우와 언더플로우 방지 방법
입력 유효성 검사: 사용자 입력을 받을 때 입력 값의 범위를 검사하여 유효한 값만 처리하도록 한다.
데이터 타입 변경: 더 큰 정수 데이터 타입을 사용하거나, 부호 없는 데이터 타입을 사용하여 정수 오버플로우를 줄일 수 있다
1. 음수를 표현하는 필요성
앞 절까지는 양수만 다루었지만, 실제로는 양수와 음수를 모두 표현해야 하는 경우가 많다. 4비트로 표현할 수 있는 값이 16가지(0~15)이지만, 이를 음수를 포함하는 표현법으로 바꿀 수 있음. 비트의 해석 방식(문맥)을 다르게 하면 같은 비트 패턴도 다른 의미를 가질 수 있음.
기억하자. 언어는 문맥과 의미를 통해 작용하기 때문에 이 말은 비트들을 해석하는 새로운 문맥을 만들 수 있다는 뜻이다.
음수와 양수를 비교하기 위해 부호를 쓰는데 부호는 +, -로 2가지 값이 있으므로 이를 비트 하나로 표현할 수 있다.
가장 왼쪽 비트(MSB, Most Significant Bit)를 부호 비트로 사용하고, 나머지 비트는 숫자의 크기(절댓값)를 나타냄.
따라서 4비트 중에 3비트가 남고, 이를 사용하면 0부터 7까지 수를 표현할 수 있다.
MSB = 0 → 양수, MSB = 1 → 음수. (부호비트)
즉, 0부터의 거리(절댓값)을 표현하기 위해 사용하는 이런 방법을 부호와 크기 표현법이라고 말한다.
예:
0111₂
→ +7
1111₂
→ -7
0000₂
→ +0
1000₂
→ -0
(0이 두 개 존재하는 문제 발생!)
문제점
① 0이 두 가지로 표현됨
0000₂
= +0
, 1000₂
= -0
같은 숫자를 표현하는 방법이 두 개 존재하여 비효율적임.
비트를 구성하려면 돈이 듦, 낭비
② 연산이 제대로 동작하지 않음
예를 들어, +1(0001₂) + (-1)(1001₂)
을 연산하면
XOR과 AND 연산을 수행하면 결과가 1010₂(-2)
가 됨.
+1과 -1을 더하면 0이 나와야 하는데, -2가 나옴 → 연산 오류 발생.
③ 추가적인 연산 로직이 필요
덧셈, 뺄셈을 정상적으로 수행하려면 별도의 복잡한 논리 연산을 추가해야 함.
이러한 복잡성을 줄이기 위해 더 나은 방법이 필요함.
부호와 크기 표현법은 0이 중복되고 연산이 자연스럽게 수행되지 않기 때문에 현대 컴퓨터에서는 사용되지 않는다.
양수의 모든 비트를 뒤집으면(반전, NOT 연산) 음수가 된다.
예:
+7(0111₂) → -7(1000₂)
+3(0011₂) → -3(1100₂)
문제점
0이 두 가지(+0, -0)로 표현됨 → 0000₂ (+0)
과 1111₂ (-0)
, 부호와 크기 표현법과 같은 문제.
덧셈 시 순환 올림(End-Around Carry) 처리가 필요 → 복잡한 하드웨어 요구. → 비용이 더 든다.
순한올림이란 MSB 쪽에서 올림이 발생한 경우에 는 LSB로 올림을 전달해야 하는 것
1의 보수도 0이 중복되고 연산 처리가 복잡하여 현대 시스템에서 사용되지 않음.
1의 보수에 1을 더하면 2의 보수가 됨. 가장 널리 사용되는 음수 표현 방식이다.
특별한 하드웨어를 추가할 수 없고 XOR과 AND 연산만 사용해야 한다면 어떻게 일반 정수 사이의 덧셈을 할 수 있을까?
+1에 더했을 때 0이 나오는 비트 패턴을 찾고 이 패턴을 -1이라고 불러보자.
4비트 수의 경우 +1은 0001이다.
1111을 0001에 더하면 0000이 된다.
앞으로는 1111을 (4비트에서) -1을 표현하는 비트 패턴으로 사용하자.
2의 보수 변환 방법
부호가 있는 정수를 표현할 때 가장 널리 쓰이는 방법이다.
비트를 반전(즉 각 비트의 NOT을 취하고)
1을 더함 → 이때 MSB에서 올림이 발생하면 이 값은 버린다.
예제: 4비트 2의 보수 표현
2의 보수(4비트)
10진수 값
0111₂
+7
0110₂
+6
0101₂
+5
0100₂
+4
0011₂
+3
0010₂
+2
0001₂
+1
0000₂
+0
1111₂
-1
1110₂
-2
1101₂
-3
1100₂
-4
1011₂
-5
1010₂
-6
1001₂
-7
1000₂
-8
+1(0001₂) → -1(1111₂)
(비트 반전 1110₂
+ 1
)
+2(0010₂) → -2(1110₂)
(비트 반전 1101₂
+ 1
)
+7(0111₂) → -7(1001₂)
0이 중복되지 않음 → 0000₂ (+0)
만 존재함.
왜 2의 보수에서는 0이 하나만 존재하는가?
부호와 크기 표현법(Sign and Magnitude)과 1의 보수(One’s Complement) 방식에서는 +0과 -0이 중복해서 존재하는 문제가 있었다.
하지만 2의 보수에서는 0이 하나만 존재한다.
확인 과정:
0000₂
(0)의 모든 비트를 반전하면 → 1111₂
(1의 보수)
여기에 1을 더하면 → 0000₂
(초기 값과 동일)
따라서 0이 하나만 존재하게 됨.
덧셈 연산이 자연스럽게 동작 → +1(0001₂) + (-1)(1111₂) = 0000₂ (0)
하드웨어 구현이 간단 → 별도의 연산 처리 없이 정수 덧셈과 동일한 방식으로 수행 가능.
N비트의 2의 보수 표현법에서는 다음과 같은 범위를 표현할 수 있다.
int, Long 등 데이터 타입의 비트 수에 따라 표현할 수 있는 값의 범위가 달라짐 그렇기 때문에 프로그래머들은 거의 본능처럼 이런 계산을 수행한다.
비트 개수
값의 개수
표현 가능한 값의 범위
4비트
16개
-8 ~ 7
8비트
256개
-128 ~ 127
16비트
65,536개
-32,768 ~ 32,767
32비트
4,294,967,296개
-2,147,483,648 ~ 2,147,483,647
64비트
18,446,744,073,709,551,616개
-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
2의 보수 표현법에서는 가장 큰 양수는 011...111 (N-1개의 1)이고, 가장 작은 음수는 100...000 (N비트에서 MSB만 1, 나머지는 0).
비트 개수가 커지면 표현할 수 있는 값의 범위가 지수적으로 증가한다.
2의 보수에서는 N비트일 때 표현할 수 있는 정수의 범위는
−(2N−1) (2N−1−1)- (2^{N-1}) \text{ ~ } (2^{N-1} - 1)
7. 음수 변환(2의 보수) 손으로 계산하는 방법
예제: -13
을 8비트 2의 보수로 변환
13
을 2진수로 변환 → 0000 1101₂
비트 반전(NOT 연산) → 1111 0010₂
1을 더함 → 1111 0011₂
(2의 보수)
-13
의 2의 보수 표현 = 1111 0011₂
같은 4비트 숫자라도 표현법에 따라 해석이 달라질 수 있다. 문맥에 따라 표현하는 값이 달라질 수 있다는 점을 꼭 염두에 둬야 한다.
예제: 1111₂
부호와 크기(Sign and Magnitude) → -7
1의 보수(One’s Complement) → -0
2의 보수(Two’s Complement) → -1
즉, 같은 2진수라도 어떤 표현법을 사용하느냐에 따라 해석이 달라지므로, 사용 중인 표현법을 정확히 알아야 한다.
2의 보수로 변환하는 방법을 익히면 손으로 직접 음수를 표현할 수 있다.
양수를 2의 보수로 변환 (예: -6)
6을 2진수로 변환 → 0110₂
비트를 반전 (NOT 연산) → 1001₂
1을 더함 → 1010₂
(즉, -6의 2의 보수 표현)
따라서 -6
의 4비트 2의 보수 표현은 1010₂
가 된다.
2의 보수를 이용한 뺄셈 (예: 5 - 3)
5의 2진수 표현: 0101₂
3의 2진수 표현: 0011₂
-3
을 구하려면 비트 반전 후 1을 더함
0011₂
→ 1100₂
(반전)
1101₂
(1을 더함 → -3
)
이제 5 + (-3)
을 계산
0101₂
+ 1101₂
결과: 10010₂
→ 5비트지만, 4비트만 보면 0010₂
즉, 2(10진수)로 정상적으로 계산됨!
1. 현대 컴퓨터가 2의 보수를 사용하는 이유
덧셈 연산을 동일한 방식으로 처리 가능 → 별도의 하드웨어 필요 없음.
0이 하나만 존재 → 저장 공간 낭비 없음.
CPU에서 음수 연산이 효율적으로 가능 → 1의 보수, 부호와 크기 방식보다 처리 속도가 빠름.
2. 프로그래밍 언어에서의 2의 보수
대부분의 프로그래밍 언어(C, Java, Python)는 2의 보수를 사용해 음수를 표현.
예제:
메모리에서 -5
는 2의 보수 형태(예: 1111 1011₂)로 저장됨.
2의 보수 표현법은 현대 컴퓨터에서 가장 널리 사용되는 음수 표현 방식이다.
0이 하나만 존재하기 때문에 불필요한 중복이 없음.
비트 수에 따라 표현할 수 있는 정수의 범위가 달라지며, 비트 수가 증가할수록 범위가 지수적으로 증가함.
같은 2진수라도 어떤 표현법을 사용하는지에 따라 해석이 달라질 수 있으므로 항상 사용 중인 표현법을 알아야 함.
2의 보수를 사용하면 덧셈과 뺄셈이 자연스럽게 이루어지며, 연산이 효율적이고 하드웨어 구현이 용이함.
컴퓨터에서 덧셈을 수행할 때 XOR(배타적 논리합, Exclusive OR)과 AND(논리곱) 연산을 활용하는 이유는 하드웨어 구현이 간단하고 효율적이기 때문이다.
만약 XOR 대신 다른 논리 연산을 사용하려 하면, 별도의 추가 연산 장치가 필요할 수도 있다.
2진수 덧셈을 살펴보면, 각 비트의 덧셈 결과는 다음과 같다.
0
0
0
0
0
1
1
0
1
0
1
0
1
1
0
1
여기서
합(Sum) = A XOR B
올림(Carry) = A AND B
이렇게 하면 덧셈을 수행하는 회로(반가산기, Half Adder)를 XOR과 AND만으로 만들 수 있다.
덧셈 결과를 결정하는 기본적인 규칙을 따르려면 다음과 같은 계산이 필요하다.
0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 0
(올림 발생)
XOR이 아니라 다른 논리 연산(예: OR, AND, NAND, NOR 등)을 사용하면 문제 발생 예를 들어, OR을 사용하면:
1 OR 1 = 1
이므로, 1 + 1의 결과가 1이 되어 잘못된 값이 나옴.
이 때문에 XOR을 사용하지 않으면 정확한 덧셈을 수행하기 위해 더 복잡한 논리 회로가 필요하게 된다.
만약 XOR을 사용하지 않고 덧셈을 수행하려면,
덧셈을 위한 맞춤형 논리 회로를 추가해야 함.
덧셈을 제대로 수행하려면 다른 조합의 논리 연산을 거쳐야 하므로, 연산 속도가 느려질 수 있음.
XOR 없이 덧셈을 하려면, 더 많은 게이트(AND, OR, NAND 등)를 사용하여 원하는 결과를 만들도록 설계해야 함.
이런 이유로 XOR이 없는 덧셈 회로를 만들면 하드웨어 복잡성이 증가하고, 기본적인 덧셈을 수행하는데 더 많은 비용과 전력 소비가 필요할 수 있다.
컴퓨터의 산술 논리 연산 장치(ALU, Arithmetic Logic Unit)
는 XOR과 AND를 조합하여 덧셈 연산을 수행한다.
반가산기(Half Adder)
입력: A, B
Sum(합) = A XOR B
Carry(올림) = A AND B
전가산기(Full Adder)
입력: A, B, 이전 자리에서 넘어온 Carry
Sum(합) = (A XOR B) XOR Carry_in
Carry_out(새로운 올림) = (A AND B) OR (Carry_in AND (A XOR B))
XOR 없이 덧셈을 구현하려면 더 복잡한 논리 연산과 조합 회로가 필요해진다.
XOR을 사용하면 덧셈 연산을 가장 간단한 논리 회로로 구현할 수 있다.
만약 XOR을 사용하지 않으면, 별도의 복잡한 논리 회로와 추가적인 하드웨어가 필요해지며, 덧셈 연산 속도도 느려질 수 있다.
이 때문에 모든 CPU와 연산 회로에서는 XOR을 기반으로 덧셈을 수행하고 있다.
지금까지는 정수를 2진수로 표현하는 방법을 살펴봤다. 그렇다면 실수는 어떻게 표현할 수 있을 까?
밑이 10인 실수에는 10진 소수점decimal point이 포함된다. 따라서 밑이 2인 경우, 실수를 표기하기 위해 2진 소수점을 표현할 방법
이 필요하다.
여기서도 정수의 경우와 마찬가지로 문맥에 따라 실수를 표현하는 방법이 달라질 수 있다.
예를 들어, 4비트를 사용한다고 하면:
2비트는 정수 부분
2비트는 소수 부분
이렇게 정해진 비트 수를 사용해 고정된 위치에서 정수와 소수를 나눠 표현한다.
정수 표현 방식과 유사함 → 일반적인 2진수 표현에서 소수점을 미리 지정한 위치에 둔다.
소수 부분은 2의 거듭제곱
으로 표현됨 → 10진 소수(1/10, 1/100 등)와 달리, 2진 소수는 1/2, 1/4, 1/8, 1/16 등으로 표현된다.
연산이 간단함 → 정해진 자리 수를 기준으로 덧셈, 뺄셈 연산을 수행할 수 있다.
고정된 범위 내에서만 표현 가능 → 표현할 수 있는 값의 범위가 제한적
표현 가능한 수의 범위가 제한됨
비트 수가 고정되어 있어, 매우 큰 수나 매우 작은 수를 정확하게 표현하기 어렵다
예를 들어, 4비트 고정소수점에서는 0~3¾
사이의 값만 표현 가능하므로, 4
이상의 값이나 1/8
같은 더 작은 수는 표현할 수 없음.
메모리 사용 비효율
더 정밀한 실수를 표현하려면 더 많은 비트가 필요하지만, 비트가 제한되어 있기 때문에 메모리 낭비가 발생할 수 있음.
예를 들어, 32비트를 사용하여 실수를 표현한다고 할 때, 16비트 정수 + 16비트 소수
로 나눈다면, 작은 값도 32비트를 차지하게 됨.
다양한 크기의 수 표현 어려움
플랑크 상수(6.63×10⁻³⁴)처럼 매우 작은 수와, 아보가드로 수(6.02×10²³)처럼 매우 큰 수를 함께 표현하기 어려움.
이를 해결하려면 몇백 비트가 필요하므로 비효율적임.
쓸모 있는 범위의 실숫값을 표현하기 위해 필요한 비트 개수가 너무 많기 때문에 범용 컴퓨터에서 이런 방식을 사용하는 경우는 드물다.
범용 컴퓨터는 일반적인 문제를 해결하기 위해 만들어진 컴퓨터라서 그런식으로 불린다.
고정소수점 표현법은 범용 컴퓨터에서는 잘 사용되지 않지만, 특정한 목적을 가진 컴퓨터(DSP, Digital Signal Processor)에서 사용되기도 한다.
디지털 신호 처리(DSP): 오디오, 영상 처리 등의 작업에서 일정한 정밀도를 유지하면서 연산 속도를 빠르게 하기 위해 사용됨.
임베디드 시스템(Embedded Systems): 메모리와 연산 자원이 제한된 환경에서는 고정소수점이 부동소수점보다 더 효율적임.
고정소수점 방식의 한계를 극복하기 위해 부동소수점(Floating-Point) 표현 방식이 등장했다.
예를 들어, 1.23 × 10³
과 같은 형태로 수를 표현하면, 큰 수나 작은 수도 효율적으로 표현할 수 있음.
IEEE 754 표준을 따라 32비트(단정도) 및 64비트(배정도) 부동소수점 방식이 범용 컴퓨터에서 사용됨.
고정소수점과 부동소수점 개념을 잘 이해하려면 10진 소수를 2진 소수로 변환하는 방법을 알아두는 것이 유용하다.
예를 들어, 0.625(10진수)
를 2진수로 변환하는 과정은 다음과 같다.
0.625 × 2 = 1.25
→ 정수 부분 1
, 소수 부분 0.25
0.25 × 2 = 0.50
→ 정수 부분 0
, 소수 부분 0.50
0.50 × 2 = 1.00
→ 정수 부분 1
, 소수 부분 0.00
결과적으로, 0.625(10진수) = 0.101(2진수)
하지만 10진수로 깔끔하게 표현되는 소수라도 2진수에서는 무한 반복되는 경우가 있음.
예를 들어, 0.1(10진수)
를 2진수로 변환하면 0.0001100110011...(반복)
형태로 끝없이 이어진다.
이런 반복 소수는 부동소수점 연산에서 오차를 발생시키는 원인 중 하나다.
부동소수점(Floating-Point) 표현법은 소수점 위치를 고정하지 않고 이동할 수 있도록 설계된 수 표현 방식이다.
이는 과학적 표기법(Scientific Notation)을 2진수에 적용한 것으로, 큰 수와 작은 수를 효과적으로 표현할 수 있다. 과학적 표기법에서는 숫자를 가수(Mantissa)와 지수(Exponent)로 나눈다.
예를 들어:
0.0012
를 과학적 표기법으로 표현하면 → 1.2 × 10⁻³
120000
을 과학적 표기법으로 표현하면 → 1.2 × 10⁵
2진법에서도 마찬가지로, 밑을 2
로 하여 표현한다.
예를 들어, 6.0
을 2진 부동소수점으로 표현하면 → 1.1 × 2²
부동소수점 수는 가수(Mantissa)와 지수(Exponent)로 구성된다.
가수(Mantissa): 실수 부분을 나타내며, 소수점 왼쪽에 한 자리만 있는 2진수로 정규화됨.
지수(Exponent): 2를 몇 번 거듭제곱해야 하는지를 나타냄.
예제 (4비트 부동소수점, 2비트 가수 + 2비트 지수)
가수 1.1
, 지수 10(₂) = 2(₁₀)
이면
실제 값 = 1.1 × 2² = 6.0(₁₀)
더 넓은 범위의 수를 표현 가능
플랑크 상수(6.63 × 10⁻³⁴)부터 아보가드로 수(6.02 × 10²³) 같은 극단적인 값을 표현할 수 있음.
고정소수점 방식과 달리, 매우 큰 수와 작은 수를 효율적으로 표현할 수 있음.
소수점의 위치를 유동적으로 조절 가능
소수점이 움직이므로 다양한 크기의 수를 표현할 수 있음.
예: 1.1 × 2³ = 8.0
, 1.1 × 2⁻² = 0.375
메모리 절약
필요한 만큼의 가수와 지수만 저장하면 되므로 고정소수점 방식보다 더 효율적임.
비효율적인 비트 사용
같은 값을 여러 개의 방법으로 표현할 수 있어 중복되는 비트 패턴이 발생.
예를 들어 1.0
과 2.0
, 4.0
같은 값은 여러 방법으로 표현 가능.
정확도가 떨어질 수 있음
부동소수점 표현 방식은 근사값을 저장하는 방식이라 정확도가 제한됨.
2진수에서 표현할 수 없는 10진 소수(예: 0.1₁₀ → 0.0001100110011…₂ 무한 반복)가 존재함.
연산 오류 발생 가능
지수가 커질수록 가수의 한 패턴과 다른 패턴 사이의 값 차이가 커진다.
예를 들어 0.5 + 6.0
같은 연산에서 6.5
를 정확히 표현할 수 없는 문제가 발생할 수 있음.
이러한 오류를 해결하기 위해 수치 해석(Numerical Analysis) 분야에서 연구가 이루어짐.
현대 컴퓨터에서는 IEEE 754 부동소수점 표준을 사용한다.
IEEE 754는 단정도(32비트)와 배정도(64비트) 부동소수점 표현 방식을 정의함.
32비트 단정도(Single Precision)
1비트: 부호(Sign)
8비트: 지수(Exponent)
23비트: 가수(Mantissa)
64비트 배정도(Double Precision)
1비트: 부호(Sign)
11비트: 지수(Exponent)
52비트: 가수(Mantissa)
IEEE 754 표준을 따르면 부동소수점 연산의 정확성과 일관성을 유지할 수 있음.
반면 12.00, 01.20, 00.12 같은 고정소수점 수에서 소수점 바로 왼 쪽의 수는 항상 일의 자리 숫자로 고정돼서 고정 소수점이다.
소수점 왼쪽이 항상 일의 자리 숫자는 게 뭔소리일까? 12는 십의 자리인데
"소수점 왼쪽이 항상 일의 자리 숫자"라는 표현이 헷갈릴 수 있는데, 이는 고정소수점 방식과 부동소수점 방식의 차이를 설명하는 맥락에서 사용된 것이다.
고정소수점에서의 소수점 위치
고정소수점(Fixed-Point) 방식에서는 소수점의 위치가 항상 정해져 있어서 변하지 않는다.
예를 들어, 만약 소수점이 항상 일의 자리 숫자(1자리) 왼쪽에 고정되어 있다면, 다음과 같이 표현될 수 있다.
12.00
01.20
00.12
이 경우, 소수점의 위치가 미리 정해져 있어서, 숫자가 변해도 소수점이 움직이지 않는다.
즉, 12
라는 숫자 자체가 아니라 소수점 왼쪽 자릿수가 항상 같은 위치에 유지된다는 의미이다.
예를 들어, 10진수 기준으로 소수점이 일의 자리 숫자 바로 오른쪽에 고정된다면, 항상 다음과 같이 표현된다.
3.45
1.23
0.12
45.67
(여기서는 십의 자리도 포함하지만, 소수점 바로 왼쪽은 5라는 일의 자리)
즉, 소수점의 위치가 항상 같은 곳에 있고, 소수점 왼쪽이 일의 자리 숫자로 유지된다는 의미이다.
반면 부동소수점(Floating-Point) 방식에서는 소수점이 유동적으로 움직일 수 있다.
이는 과학적 표기법(Scientific Notation)
과 같은 방식으로 숫자를 표현하기 때문이다.
예를 들어, 1.2라는 가수를 사용하여 다른 숫자들을 표현하면
1.2 × 10⁰ = 1.2
1.2 × 10¹ = 12
1.2 × 10² = 120
1.2 × 10⁻¹ = 0.12
같은 1.2
라는 숫자라도, 소수점의 위치가 변하면서 숫자의 크기가 달라질 수 있다.
이와 마찬가지로, 2진수 부동소수점에서도 소수점이 가수(Mantissa)와 지수(Exponent)에 의해 달라지는 것이다.
1.10 × 2³ = 11.0₂ (10진수 6)
1.10 × 2⁻² = 0.0110₂ (10진수 0.375)
1.10 × 2⁵ = 110000.0₂ (10진수 48)
즉, 고정소수점에서는 숫자가 커져도 소수점의 위치가 일정하지만, 부동소수점에서는 숫자의 크기에 따라 소수점의 위치가 바뀐다는 차이가 있다.
고정소수점에서는 소수점의 위치가 고정되어 있어서 소수점 왼쪽 자릿수가 항상 같은 위치에 존재한다.
즉, 특정한 자리수(예: 일의 자리)에서 소수점이 고정되어 변하지 않는다.
부동소수점에서는 소수점이 숫자의 크기에 따라 이동할 수 있기 때문에, 소수점 왼쪽 숫자가 항상 같은 자리에 있지 않는다.
즉, 같은 숫자라도 소수점이 움직이므로 다양한 크기의 값을 표현할 수 있다.
따라서 소수점 왼쪽이 항상 일의 자리 숫자다라는 말은, 고정소수점에서는 소수점이 항상 같은 위치에 있어서, 숫자의 자릿수가 변해도 소수점 왼쪽의 숫자 위치는 일정하다는 의미로 이해하면 된다.
부동소수점 표현법은 소수점의 위치를 고정하지 않고 유동적으로 조절하는 방식이다.
고정소수점보다 훨씬 넓은 범위의 숫자를 표현할 수 있어, 현대 컴퓨터에서 널리 사용된다. 하지만 정확도의 한계와 연산 오류 가능성이 있으며, 이를 해결하기 위해 IEEE 754 표준이 도입되었다.
부동소수점 연산을 다룰 때는 반올림 오차, 정밀도 손실, 수치 해석 기법 등을 고려해야 한다.
이제 부동소수점 개념이 이해되었으면,
IEEE 754 부동소수점 표준의 내부 구조와 동작 방식
부동소수점 연산에서 발생하는 오차와 그 해결 방법
컴퓨터에서 실수 연산을 다룰 때 주의할 점
등을 배우면 더욱 깊이 있는 이해가 가능하다. 🚀
이 표준은 단정도(32비트)와 배정도(64비트) 부동소수점을 정의하며, 부동소수점 연산의 정확성을 높이기 위한 여러 기법이 포함되어 있다.
부동소수점은 표 1-8보다 더 많은 비트를 사용하며, 가수와 지수에 대해 각각 부호 비트를 사용한다 지수에 대한 부호 비트는 지수의 비트 패턴에 감춰져 있다.
낭비되는 비트 조합을 최소화하고 반올림을 쉽게 하기 위한 여러 가지 트릭이 사용된다.
IEEE 754라는 표준은 이 모든 기능을 정의한다.
1비트
단정도: 8비트 / 배정도: 11비트
단정도: 23비트 / 배정도: 52비트
1비트 (부호, Sign) → 0
이면 양수, 1
이면 음수
8비트 (지수, Exponent) → 2의 거듭제곱을 나타내며, Bias를 적용하여 부호 비트 없이 표현
23비트 (가수, Mantissa) → 유효숫자로, 정규화(normalization) 기법을 적용하여 저장 공간을 절약
실제 값=(−1)부호×(1+가수)×2(지수−Bias)\text{실제 값} = (-1)^{\text{부호}} \times (1 + \text{가수}) \times 2^{(\text{지수} - \text{Bias})}
단정도(32비트) → Bias: 127
배정도(64비트) → Bias: 1023
예제: 0 10000001 10100000000000000000000
(단정도 32비트)
부호: 0
(양수)
지수: 10000001₂ = 129
, 실제 지수 = 129 - 127 = 2
가수: 1.101₂ = 1 + 0.5 + 0.125 = 1.625
값: 1.625 × 2² = 6.5
IEEE 754는 똑같은 비트를 사용하면서도 최대한 정밀도를 높이는 다양한 기법을 적용한다.
(1) 정규화(Normalization)
가수의 왼쪽(맨 앞) 비트가 항상 1이 되도록 조정하여 불필요한 0을 제거하고 비트 공간을 절약.
예제: 0.01101 × 2⁵
→ 1.101 × 2²
원래는 01101.0000₂
이지만, 왼쪽 첫 번째 1이 가수의 시작이 되도록 소수점을 이동하고 지수를 조정.
(2) 암시적 1비트 (Hidden Bit)
가수의 맨 앞 비트는 항상 1이므로 저장하지 않고 생략, 대신 필요한 추가 정보를 저장할 수 있도록 비트를 절약
단정도에서는 23비트 가수지만 실제로는 24비트(1비트가 암시적 1비트로 존재).
디지털 이큅먼트 사DEC, Digital Equipment Corporation에서 고안한 것
가수의 맨 왼쪽 비트가 1이라는 사실을 알고 있으므로 이를 생략하는 것
이로 인해 가수에 1비트를 더 사용할 수 있다
IEEE 754의 세부 사항을 모두 알 필요는 (아직은) 없다. 하지만 두 가지 부동소수점 수가 자주 쓰인다는 사실을 알아둬야 한다.
부호 비트
1비트
1비트
지수 비트
8비트 (Bias = 127)
11비트 (Bias = 1023)
가수 비트
23비트 (24비트 효과)
52비트 (53비트 효과)
정밀도
약 7자리 (10진수)
약 15자리 (10진수)
표현 가능한 범위
±10³⁸ 정도
±10³⁰⁸ 정도
메모리 사용량
적음 (일반적인 연산에 적합)
큼 (과학 계산, 금융 연산에 적합)
그림에서 2배 정밀도 수가 기본 정밀도 수보다 지수가 3비트 더 크다는 점을 볼 수 있다.
따라서 지수의 범위 는 8배 더 크다.
2배 정밀도 수
는 기본 정밀도 수보다 가수가 29비트 더 크다. 따라서 정밀도도 훨씬 더 크다. 하지만 이런 모든 장점은 비트를 2배나 더 많이 사용한다는 비용을 지불하고 얻은 것이다.
지수에 대해 부호 비트가 따로 존재하지 않는다.
IEEE 754 를 설계한 사람들은 지수 비트가 모두 0이거나 1인 경우에 특별한 의미를 갖게 하고, 실제 지숫 값은 나머지 비트 패턴에 집어넣고 싶었다. 그래서IEEE 754는 특별한 비트 패턴을 사용하여 특정한 연산 결과를 처리할 수 있도록 설계되었다.
(1) 편향된(Biased) 지수
지수 값에 Bias(편향값)를 더해 부호 비트를 없애고, 정렬을 쉽게 함.
단정도에서는 Bias = 127, 배정도에서는 Bias = 1023 사용.
127(01111111₂)
→ 실제 지수 0
1(00000001₂)
→ 실제 지수 -126
254(11111110₂)
→ 실제 지수 +127
(2) 0으로 나누는 경우의 처리
IEEE 754는 0으로 나누는 연산을 수행할 때 양/음의 무한대(∞)를 표현하는 특별한 비트 패턴을 제공.
+∞
, -∞
을 명확히 정의하여 에러 처리를 쉽게 만듦.
(3) NaN (Not a Number)
0/0
, √(-1)
같은 정의되지 않은 연산이 발생하면 NaN(Not a Number)을 반환.
NaN 값이 포함된 계산은 전파되어, 후속 연산에서도 NaN을 유지함.
부동소수점 수로 계산을 하던 도중에 NaN 값이 생기면 뭔가 잘못된 산술 연산을 수행했다는 뜻
이런 특별한 비트 패턴들은 앞에서 이야기한 특별한 지숫값(모든 비트가 0이거나 모든 비트가 1인 지수)을 사용
부동소수점 연산은 비트 수가 제한된 환경에서 실수를 표현해야 하므로, 오차가 발생할 수 있다.
(1) 표현할 수 없는 값
10진수 0.1
을 2진수로 변환하면 무한 반복(0.0001100110011...)되므로, 근사값으로 저장됨.
이로 인해 0.1 + 0.2 ≠ 0.3
같은 연산 오차가 발생할 수 있음.
(2) 반올림 오차 (Rounding Error)
저장할 수 있는 비트 수가 제한되므로 반올림이 발생하고, 작은 숫자가 더해질 때 정밀도가 떨어질 수 있음.
(3) 연산 순서에 따른 오차
(A + B) + C ≠ A + (B + C) 가 될 수 있음.
큰 숫자와 작은 숫자를 더할 때, 작은 숫자가 반올림으로 인해 손실될 수도 있음.
부동소수점 연산의 오차를 줄이기 위해 여러 기법을 사용할 수 있다.
(1) Kahan Summation Algorithm
작은 값이 반올림으로 손실되는 문제를 해결하는 알고리즘.
(2) decimal
모듈 사용 (Python)
(3) 정수 연산 활용
금융 연산에서는 소수를 사용하지 않고 정수 단위로 변환 후 연산하는 것이 일반적임.
IEEE 754 부동소수점 표준은 실수를 표현하는 방식으로, 부호(1비트), 지수(8비트), 가수(23비트)로 구성됨.
Bias를 적용한 지수 표현과 정규화된 가수를 사용하여 저장 공간을 효율적으로 활용함.
단정도(32비트)는 일반적인 연산에 적합하고, 배정도(64비트)는 높은 정밀도가 필요한 과학 및 금융 연산에 적합.
부동소수점 연산에서는 표현할 수 없는 값, 반올림 오차, 연산 순서에 따른 오차가 발생할 수 있음.
Kahan Summation, decimal
모듈 사용, 정수 연산 활용 등의 방법으로 오차를 줄일 수 있음.
(1) 비교 연산에서 ==
사용 금지
if (x == 0.1)
같은 비교는 오차 때문에 잘못된 결과를 낼 수 있음.
대신 허용 오차(ε)를 활용한 비교를 해야 함.
(2) 누적 오차 고려
반복 연산이 많아질수록 오차가 누적될 수 있으므로, 정밀도를 고려해야 함.
(3) 정확한 계산이 필요한 경우 정수 연산 사용
금융 계산에서는 소수를 직접 사용하지 않고, 정수 단위(예: 센트 단위)로 변환하는 것이 일반적임.
(4) 알고리즘 설계 시 오차를 감안한 전략 필요
수치 해석(Numerical Analysis) 기법을 활용하여 부동소수점 오차를 최소화하는 알고리즘을 선택해야 함.
1. BCD란?
BCD(Binary-Coded Decimal)는 4비트를 사용하여 10진 숫자(0~9)를 개별적으로 표현하는 방식이다.
즉, 각 10진 자리(일의 자리, 십의 자리 등)를 4비트 이진수로 변환하여 저장한다.
예제:
10진수 12
일반적인 2진수 표현: 1100₂
BCD 표현: 0001 0010₂
(1
은 십의 자리, 2
는 일의 자리)
BCD 방식은 사람이 읽고 해석하기 쉬운 장점이 있어, 디지털 디스플레이나 센서 데이터 처리 등에 사용된다.
2. BCD의 장점
10진수와의 직관적인 호환성
사람에게 익숙한 10진수와 직접적으로 매핑되므로, 디지털 디스플레이 장치에서 사용하기 편리함.
예를 들어, 127
을 BCD로 표현하면 0001 0010 0111
로 변환되며, 각 4비트 단위로 10진수를 쉽게 읽을 수 있음.
디지털 기기에서 유용
디지털 시계, 계산기, 계측기, 센서 등에서 BCD를 사용하면 숫자를 그대로 출력하기 쉬움.
예를 들어, LCD 디스플레이에서 숫자를 표시할 때, BCD를 사용하면 변환 과정이 간단해짐.
3. BCD의 단점
비트 낭비
4비트는 0000₂
(0)부터 1111₂
(15)까지 16가지 값을 표현할 수 있지만, BCD에서는 10진수 0~9까지만 사용.
즉, 6가지 조합(1010₂
~1111₂
)은 사용되지 않으며, 전체 비트의 37.5%가 낭비됨.
연산 비효율성
일반적인 2진수 연산과 다르게, BCD 연산(덧셈, 뺄셈 등)은 별도의 보정이 필요함.
예를 들어, BCD 덧셈에서 9 + 1 = 10
이 되면, 10을 BCD로 변환(0001 0000₂
)하는 추가 과정이 필요.
따라서 CPU가 직접 BCD 연산을 지원하지 않으면, 소프트웨어적으로 처리해야 하는 부담이 생김.
4. BCD의 사용 감소
과거 일부 컴퓨터들은 BCD를 직접 지원하는 명령어를 제공했지만, 오늘날에는 거의 사용되지 않음. 그 이유는 일반적인 2진수 표현보다 비효율적이기 때문이다.
과거:
컴퓨터 성능이 낮고, 비트 비용이 높았을 때 디지털 디스플레이 장치와의 호환성 때문에 BCD가 사용됨.
예를 들어, 초창기 IBM 메인프레임 일부 모델에서는 BCD를 하드웨어적으로 지원.
현재:
비트 비용이 낮아졌으며, BCD의 비효율성이 크기 때문에 대부분 2진수 연산으로 대체됨.
일부 센서나 디지털 디스플레이 장치에서 BCD가 사용되지만, 연산 처리는 대부분 일반적인 2진수 방식으로 변환하여 수행.
사람들은 2진수를 더 읽기 쉽게 표현할 수 있는 방법을 고안해냈다.
8진 표현법은 2진수 비트들을 3개씩 그룹으로 묶는 아이디어
100이라는 2진 값을 8진 숫자로 바꾸기 위해서는 (1×22 )+ (0×21 ) + (0×20 ) = 4라는 식으로 계산
현재 널리 쓰이고 있는 진법으로 밑이 16이라는 뜻이다.
현재 널리 쓰이고 있는 이유는 컴퓨터 내부가 8비트의 배수로를 사용해 만들어지기 떄문이다.
8비트의 배수는 4(16진수의 한 자리의 비트 수)로 균일하게 나눠지지만 3(8진수의 한 자리의 비트 수)로는 나눠지지 않는다.
10진 숫자 기호 중 16진수를 표현하라면 6개가 더 필요해서 abcdef라는 기호가 숫자를 표현하기로 한 것이다.
이 예제에서 비트를 4개씩 그룹으로 나눈다.
수를 표현하는 방법은 원래는 아래 첨자로 나타내서 일 수 있지만 컴퓨터 키보드로는 입력하기 불편하기에 입력하기 편한 일관성 있는 표기법을 사용하면 좋다.
✅ 진법별 숫자 표기 규칙
8진수 (Octal)
0
(0으로 시작)
017
15 (10진수)
10진수 (Decimal)
없음 (1~9로 시작)
123
123 (10진수)
16진수 (Hexadecimal)
0x
0x12f
303 (10진수)
2진수 (Binary)
0b
(C++, Python 등 일부 언어 지원)
0b1010
10 (10진수)
📌 설명 및 특징
8진수 (Octal)
숫자가 0
으로 시작하면 8진수로 간주됨.
예: 017
→ 10진수로 변환하면 1×8^1 + 7×8^0 = 8 + 7 = 15
10진수 (Decimal)
일반적으로 사용되는 숫자 시스템으로, 1~9로 시작하는 숫자는 10진수로 해석됨.
예: 123
→ 그대로 123
(10진수)
16진수 (Hexadecimal)
0x
를 앞에 붙이면 16진수로 표현됨.
예: 0x12f
→ 10진수 변환 1×16^2 + 2×16^1 + 15×16^0 = 256 + 32 + 15 = 303
2진수 (Binary)
일부 프로그래밍 언어에서 0b
접두사를 사용하여 2진수를 표현함.
예: 0b1010
→ 10진수 변환 1×2^3 + 0×2^2 + 1×2^1 + 0×2^0 = 8 + 0 + 2 + 0 = 10
⚠️ 주의할 점
0이 단독으로 사용될 경우, 8진수인지 10진수인지 구분할 필요가 없음 → 어차피 값은 0
대부분의 프로그래밍 언어는 2진수 표기를 기본적으로 제공하지 않음 (C++, Python 등 일부 언어만 0b
지원)
🚀 결론
프로그래밍에서 숫자의 진법을 정확히 이해하고 사용해야 오류를 방지할 수 있음.
8진수는 사용이 줄어들고 있으며, 현대 프로그래밍에서는 주로 10진수와 16진수를 사용.
16진수는 메모리 주소, 색상 코드, 비트 연산 등에서 널리 활용됨.
2진수는 직접적으로 사용할 일은 적지만, 일부 언어에서는 0b
접두사로 표현 가능.
✅ 즉, 숫자의 형태와 접두사를 보고 진법을 올바르게 해석하는 것이 중요하다!
컴퓨터는 무작위로 비트를 배치하지 않음 → 컴퓨터를 설계하는 사람은비용을 고려하여 비트 개수와 조직을 결정
비트는 너무 작아서 직접 사용하기 어렵기 때문에, 더 큰 단위로 묶어서 사용
과거 다양한 비트 단위가 존재했으나 현재는 8비트(바이트, byte)가 표준
예시:
Honeywell 6000 → 36비트 단위, 18비트/9비트/6비트로 분할 가능
DEC PDP-8 → 12비트 단위 사용
현재 → 8비트(1바이트, byte)가 표준
이름
비트 개수
설명
니블(Nibble)
4비트
바이트의 절반
바이트(Byte)
8비트
문자, 데이터의 기본 단위
하프 워드(Half Word)
16비트
2바이트(16비트)
워드(Word)
32비트
4바이트(32비트)
더블 워드(Double Word)
64비트
8바이트(64비트)
워드(Word): 컴퓨터가 가장 자연스럽게 처리할 수 있는 비트 묶음 크기
자연스럽게 쓸 수 있다는 말은 컴퓨터가 빠르게 처리할 수 있는 가장 큰 덩어리
예: PDP-11 → 내부적으로 16비트 사용
int
자료형의 크기는 컴퓨터의 워드 크기에 따라 달라짐
C/C++에서는 uint8, int16, int32, DWORD, INT32
같은 타입을 제공
킬로(kilo), 메가(mega), 기가(giga) 등의 단위는 원래 10진수(10^x)였지만, 컴퓨터에서는 2진수(2^x)로 사용됨.
📌 기존(전통적인) 컴퓨터 단위
컴퓨터 엔진니어들은 용어를 빌려와서 사용하되 의미를 약간 바꿔서 밑이 10이 아니라 2인 값을 표현했으며, 실제로는 1000을 뜻하지 않고, 밑이 2면서 1000에 가장 가까운 수라 1024이다.
단위
2진수 값
10진수 값 (디스크 크기 표기)
킬로바이트 (KB)
2¹⁰ = 1,024
바이트
10³ = 1,000
바이트
메가바이트 (MB)
2²⁰ = 1,048,576
바이트
10⁶ = 1,000,000
바이트
기가바이트 (GB)
2³⁰ = 1,073,741,824
바이트
10⁹ = 1,000,000,000
바이트
테라바이트 (TB)
2⁴⁰ = 1,099,511,627,776
바이트
10¹² = 1,000,000,000,000
바이트
문제: 일부 제조사(특히 디스크 크기 표기)에서 10진수(1,000) 단위를 사용하여 혼동 발생 → 예: 1TB 하드디스크의 실제 크기가 운영체제에서 931GiB로 보이는 이유
기존 단위(킬로, 메가, 기가 등)의 혼동을 방지하기 위해 새로운 단위가 도입됨.
새로운 단위 (IEC 표준)
2진수 값 (정확한 표현)
기존 단위 (혼용된 표현)
키비바이트 (KiB)
2¹⁰ = 1,024
바이트
1KB (전통적 의미)
메비바이트 (MiB)
2²⁰ = 1,048,576
바이트
1MB
기비바이트 (GiB)
2³⁰ = 1,073,741,824
바이트
1GB
테비바이트 (TiB)
2⁴⁰ = 1,099,511,627,776
바이트
1TB
IEC 단위(키비, 메비, 기비)는 정확한 2진수 기반 크기를 의미함.
점점 더 많은 기술 문서 및 운영체제에서 IEC 표준(KiB, MiB, GiB, TiB) 를 사용 중.
과거: 문자(Character) = 1바이트(Byte) → ASCII(7비트, 1바이트로 표현 가능) 사용
현재: 다국어 지원을 위해 문자가 1바이트로 표현되지 않는 경우 증가 → UTF-8, UTF-16, UTF-32 등 멀티 바이트 문자 인코딩 등장
예: 한글 "가" → UTF-8에서는 3바이트 사용
비트
는 작은 단위이므로, 컴퓨터는 이를 더 큰 단위(니블, 바이트, 워드 등)로 조직하여 사용.
현재 대부분의 컴퓨터는 8비트(1바이트)를 기본 단위로 사용.
데이터 크기 단위(킬로, 메가, 기가 등)는 원래 10진수였으나, 컴퓨터에서는 2진수(1024 단위)로 사용됨.
IEC 표준(KiB, MiB, GiB, TiB)이 도입되었으나, 기존 단위와 혼용되는 경우가 많아 주의해야 함.
문자는 1바이트로 표현되지 않을 수 있으며, UTF-8과 같은 멀티 바이트 인코딩이 널리 사용됨.
컴퓨터에서 데이터 크기를 다룰 때는 반드시 2진수와 10진수의 차이를 이해하고 문맥을 고려해야 한다.
수를 사용해 문자나 키보드에 있는 다른 기호 등을 표현하는 방법
ASCII (American Standard Code for Information Interchange, 미국 표준 코드) 키보드 문자 및 기호를 7비트 숫자로 표현하는 표준 인코딩 방식 1963년부터 표준화 → 현재도 기본적인 문자 인코딩 방식으로 사용됨
각 문자(알파벳, 숫자, 특수 기호)에 7비트(0~127)의 숫자 할당
예제:
A → 10진수 65
, 16진수 0x41
, 8진수 0101
B → 10진수 66
, 16진수 0x42
공백(Space, SP) → 10진수 32
, 16진수 0x20
경쟁했던 다른 인코딩 방식:
IBM의 EBCDIC(엡시딕, Extended Binary-Coded Decimal Interchange Code) -> ‘BCD’는 앞에서 살펴본 2진 코드화한 10진수를 뜻함
하지만 ASCII가 표준이 됨.
10진수
16진수
문자
설명
32
20
SP
공백(Space)
48
30
0
숫자 0
49
31
1
숫자 1
65
41
A
대문자 A
66
42
B
대문자 B
97
61
a
소문자 a
98
62
b
소문자 b
126
7E
~
물결 기호
127
7F
DEL
삭제(Delete)
ASCII 코드의 문자들은 7비트(0~127) 내에서 표현됨.
ASCII 코드 중 화면에 출력되지 않고, 장치를 제어하는 문자들 통신, 데이터 처리, 프린터 제어 등에 사용됨
기호
10진수
설명
NUL
0
널(null) 문자
BEL
7
벨(Bell) → 소리 알림
BS
8
백스페이스(Backspace)
HT
9
가로 탭(Horizontal Tab)
NL (LF)
10
새 줄(Line Feed)
CR
13
캐리지 리턴(Carriage Return)
ESC
27
이스케이프(Escape)
DEL
127
삭제(Delete)
LF(새 줄)
+ CR(캐리지 리턴)
윈도우: CR + LF
(줄바꿈 → \r
)
유닉스/리눅스: LF
()
맥OS (구버전): CR
()
ASCII는 영어 기반(128개 문자) → 한글, 일본어, 중국어 표현 불가
확장 ASCII(8비트, 256개 문자) 등장 → ISO-8859, Windows-1252
이후 UTF-8(가변 길이, 유니코드 지원) 등장 → 국제 표준으로 자리 잡음
초기 컴퓨터에서 사용된 문자 인코딩 방식.
7비트(0~127) 로 구성되어 영어 알파벳, 숫자, 특수 문자만 표현 가능.
미국에서 개발되었으며, 대부분의 초기 컴퓨터가 미국산 또는 영국산이었기 때문에 문제없었음.
컴퓨터가 전 세계적으로 보급됨에 따라 영어 이외의 문자(예: 유럽어, 아시아 문자)를 지원해야 하는 문제가 발생.
ISO 표준 확장
ISO-646 : 아스키를 확장하여 일부 유럽 언어의 액센트(é, à, ü 등) 및 발음 구별 기호 추가.
ISO-8859 : 다양한 언어(서유럽어, 동유럽어, 아랍어 등)를 지원하는 8비트 문자 세트.
각국의 문자 인코딩
JIS X 0201 : 일본어 표현을 위한 일본 산업 표준(JIS).
KS C 5601 : 한국어 표현을 위한 한국 문자 코드 표준.
GB2312 : 중국어 지원을 위한 중국 문자 코드.
각 국가마다 서로 다른 문자 인코딩 방식을 사용하다 보니 문자 간 호환성이 떨어지는 문제 발생.
💡 유니코드의 등장
유니코드는 과거의 여러 문자 인코딩 문제를 해결하고, 전 세계 언어와 기호(이모지 포함)를 통합하는 표준이다.
16비트(0~65535) 기반의 새로운 문자 표준 도입.
기존 아스키(7비트)보다 훨씬 많은 문자를 표현 가능하여 모든 언어를 지원할 수 있도록 설계됨.
초기에는 16비트면 충분하다고 생각했지만, 예상보다 많은 언어와 문자(한자, 이모지 등)가 추가됨.
유니코드 확장 (21비트)
현재 유니코드는 21비트(1,112,064개 코드) 까지 확장됨.
현대에는 고양이 이모티콘 🐱, 다양한 기호(⚡, 🎵, 🏆) 등도 포함되어 있음.
아스키(ASCII) : 영어만 표현 가능 (7비트)
ISO 표준(ISO-646, ISO-8859 등) : 유럽 언어 및 일부 확장
각국 문자 인코딩(JIS, KS C 5601 등) : 일본어, 한국어, 중국어 지원
유니코드(Unicode) : 16비트 → 21비트 확장, 전 세계 모든 문자 지원
컴퓨터는 기본적으로 8비트(1바이트) 단위로 데이터를 처리함.
아스키(ASCII) 문자는 원래 7비트로 설계되었지만, 컴퓨터가 8비트를 기본 단위로 사용하므로 1바이트(8비트)로 저장됨.
8비트만 사용하면 대부분의 영어 문자(ASCII)는 표현 가능하지만, 한글, 중국어, 일본어 등은 표현 불가능.
이를 해결하기 위해 유니코드(Unicode)와 다양한 인코딩 방식(UTF-8, UTF-16, UTF-32)가 등장.
우리는 비트
같은 추상화를 사용해 숫자
를 표현하고, 숫자를 사용해 문자
를 표현하고, 다시 다른 숫자를 사용해 이런 숫자
를 표현한다.
유니코드 (Unicode): 전 세계 모든 문자를 고유한 숫자 코드(코드 포인트)로 표현하는 표준
하지만, 유니코드 자체는 단순한 숫자 코드일 뿐 → 컴퓨터에서 사용하려면 인코딩(Encoding) 방식이 필요함.
인코딩(Encoding)
이란?
특정 비트 패턴을 사용하여 유니코드 문자(코드 포인트)를 저장하는 방법.
💡 대표적인 인코딩 방식
UTF-8 (Unicode Transformation Format - 8 bit)
가장 널리 쓰이는 인코딩 방식.
아스키(ASCII)와 완벽한 하위 호환을 제공 뿐만 아니라 효울성이 좋음
문자마다 다른 크기의 바이트(1~4바이트)
를 사용하여 저장
아스키가 아닌 문자의 경우 아스키를 받아서 처리하는 프로그램이 깨지지 않는 방법으로 문자 인코등
UTF-16
16비트(2바이트) 단위로 문자를 저장.
아스키 문자도 16비트로 표현 → 비효율적일 수 있음.
일부 문자는 4바이트(2개의 16비트 블록)를 사용함.
UTF-32
모든 문자를 32비트(4바이트)로 표현.
고정된 크기이므로 단순하지만, 메모리 사용량이 많음.
UTF-8은 문자의 코드 포인트 크기에 따라 가변 길이(1~4바이트)를 사용함.
유니코드 범위
UTF-8 바이트 수
비트 수
예시 문자
U+0000 ~ U+007F
1바이트
7비트
A (U+0041)
U+0080 ~ U+07FF
2바이트
11비트
π (U+03C0)
U+0800 ~ U+FFFF
3바이트
16비트
♣ (U+2663)
U+10000 ~ U+10FFFF
4바이트
21비트
🐱 (U+1F431)
💡 UTF-8의 특징
영어(ASCII) 문자는 1바이트(8비트)로 저장 → 기존 아스키(ASCII)와 호환됨.
유럽어, 그리스어, 아랍어 등은 2바이트.
한글, 중국어, 일본어 문자 등은 3바이트.
이모지 같은 확장 문자는 4바이트.
A
는 ASCII에 포함 → UTF-8로 인코딩 시 1바이트 사용.
유니코드 코드 포인트: U+0041
이진수 표현: 00000000 01000001
UTF-8 인코딩 결과: 0x41
✅ π (U+03C0)의 경우
유니코드 코드 포인트: U+03C0
이진수 표현: 00000011 11000000
UTF-8 인코딩:
2바이트로 표현해야 함 → 첫 바이트는 110xxxxx
, 두 번째 바이트는 10xxxxxx
결과: 11001111 10000000
(0xCF 0x80
)
✅ ♣ (U+2663)의 경우
유니코드 코드 포인트: U+2663
이진수 표현: 00100110 01100011
UTF-8 인코딩:
3바이트로 표현해야 함 → 첫 바이트는 1110xxxx
, 두 번째/세 번째 바이트는 10xxxxxx
결과: 11100010 10011001 10100011
(0xE2 0x99 0xA3
)
✅ 🐱 (U+1F431)의 경우
유니코드 코드 포인트: U+1F431
이진수 표현: 00011111 01000011 00010001
UTF-8 인코딩:
4바이트로 표현해야 함 → 첫 바이트는 11110xxx
, 이후는 10xxxxxx
결과: 11110000 10011111 10010000 10110001
(0xF0 0x9F 0x90 0xB1
)
✅ 아스키(ASCII)와 100% 호환됨 → 영어 사용자는 추가 메모리 소모 없이 기존 방식 유지. ✅ 가변 길이 인코딩 → 공간을 효율적으로 사용 가능. ✅ 모든 유니코드 문자 표현 가능 → 한글, 일본어, 중국어, 이모지 모두 표현 가능. ✅ 널리 사용됨 → 거의 모든 운영체제(OS)와 웹 브라우저가 기본적으로 UTF-8을 지원.
컴퓨터가 기본적으로 8비트를 사용하기 때문에 아스키(ASCII)는 8비트로 저장.
아스키(ASCII) 문자만 사용할 경우, UTF-8은 동일한 8비트를 사용하여 공간 낭비가 없음.
UTF-8은 문자에 따라 1~4바이트를 사용하여 전 세계 모든 문자를 효율적으로 표현.
첫 바이트의 비트 패턴을 통해 문자 경계를 쉽게 구분할 수 있음.
이모지(🐱)와 같은 확장 문자는 4바이트로 표현됨.
여기서 잠깐 : 왜 일부 문자는 1바이트(8비트), 다른 문자는 4바이트(32비트)인가? 분명 한 문 자를 8비트로 표현한다고 했는데?
기본 개념: 바이트와 비트
1바이트(Byte) = 8비트(Bit)
2바이트 = 16비트
3바이트 = 24비트
4바이트 = 32비트
💡 즉, "4바이트"라고 하면 32비트(4 × 8)라는 뜻! 하지만, 4바이트로 표현된다고 해서 기본적으로 16비트를 의미하는 것은 아님.
문자(유니코드)는 크기가 다르다!
컴퓨터에서 문자를 저장하는 방식은 유니코드(Unicode) + 인코딩(UTF-8, UTF-16, UTF-32)에 따라 달라짐.
영어 알파벳 (A, B, C 등) → 1바이트(8비트)
유럽어, 아랍어, 그리스어 문자 → 2바이트(16비트)
한글, 중국어, 일본어 등 → 3바이트(24비트)
이모지(🐱, 😂, 🚀 등) → 4바이트(32비트)
✅ 즉, 문자의 종류에 따라 필요한 크기가 다르다! 이 때문에 일부 문자는 1바이트(8비트), 일부 문자는 4바이트(32비트)를 사용한다.
UTF-8은 가변 길이 인코딩 방식
UTF-8은 문자마다 필요한 만큼 바이트(1~4바이트)를 할당하는 가변 길이 인코딩을 사용.
유니코드 범위
UTF-8 바이트 수
비트 수
예시 문자
U+0000 ~ U+007F
1바이트
8비트
A (U+0041)
U+0080 ~ U+07FF
2바이트
16비트
π (U+03C0)
U+0800 ~ U+FFFF
3바이트
24비트
한글 '가' (U+AC00)
U+10000 ~ U+10FFFF
4바이트
32비트
🐱 (U+1F431)
💡 즉, UTF-8은 문자의 복잡도에 따라 1~4바이트를 동적으로 사용하여 저장!
왜 일부 문자는 4바이트(32비트)로 저장될까?
이모지(🐱, 😂, 🚀) 같은 특수 문자는 유니코드 코드 포인트가 크기 때문에 4바이트가 필요함.
예를 들어 🐱(고양이 이모지)의 유니코드 값:
U+1F431 → 16진수로 보면 5자리 숫자
8비트(1바이트)로 표현할 수 없음 → 4바이트(32비트) 필요
💡 즉, 단순한 문자(영어, 숫자)는 1바이트로 충분하지만, 복잡한 문자는 더 많은 바이트(2~4바이트)가 필요!
UTF-8의 장점: 왜 가변 길이 인코딩을 사용할까?
공간 절약: 영어만 사용하면 1바이트만 사용 → 불필요한 공간 낭비 방지.
유연성: 모든 언어(한글, 중국어, 일본어, 이모지 등)를 저장 가능.
아스키(ASCII)와 호환: 기존 영어 기반 시스템에서도 그대로 사용 가능.
📌 문자마다 저장 크기가 다름 → 간단한 문자는 1바이트, 복잡한 문자는 4바이트 사용. 📌 UTF-8은 문자의 복잡도에 따라 1~4바이트를 동적으로 할당하는 방식. 📌 영어(A, B, C)는 1바이트(8비트), 한글(가, 나, 다)은 3바이트(24비트), 이모지(🐱, 🚀)는 4바이트(32비트). 📌 UTF-8은 공간 절약 + 아스키(ASCII)와 호환되는 최적의 인코딩 방식!
UTF-8은 가변 길이 인코딩 방식을 사용해 최소 1바이트에서 최대 4바이트까지 동적으로 확장할 수 있다.
UTF-8의 비트 구조
UTF-8에서 첫 번째 바이트의 앞쪽 비트(MSB, Most Significant Bit)
를 이용해서 이 문자가 몇 바이트로 이루어졌는지를 알려줍니다.
즉, 첫 바이트의 앞쪽 비트(접두사) 보고 이 문자가 1~4바이트 중 몇 바이트를 사용하는지를 알 수 있음!
아스키 (ASCII), 문자(0~178)
U+0000 ~ U+007F
1바이트
0xxxxxxx
기본 라틴 문자 외의 문자(라틴, 아랍ㅇㅓ) : 128~2047
U+0080 ~ U+07FF
2바이트
110xxxxx 10xxxxxx
한자, 한글 등 : 2048~65535
U+0800 ~ U+FFFF
3바이트
1110xxxx 10xxxxxx 10xxxxxx
이모지, 복잡한 문자 : 65536~10FFFF
U+10000 ~ U+10FFFF
4바이트
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
2. 바이트 수를 어떻게 결정하는가?
UTF-8에서 문자 인코딩을 결정하는 첫 번째 바이트 패턴을 보면 다음과 같다.
1️⃣ 1바이트 문자 (ASCII, 7비트)
형식: 0xxxxxxx
예: A
(U+0041
→ 0x41
)
A
는 아스키 문자이므로 1바이트만 사용
2️⃣ 2바이트 문자 (확장 라틴 문자 등)
형식: 110xxxxx 10xxxxxx
예: é
(U+00E9
→ 0xC3 0xA9
)
첫 바이트가 110
으로 시작하면 2바이트 사용
3️⃣ 3바이트 문자 (한자, 한글, 특수 기호 등)
형식: 1110xxxx 10xxxxxx 10xxxxxx
예: 가
(U+AC00
→ 0xEA 0xB0 0x80
)
첫 바이트가 1110
으로 시작하면 3바이트 사용
4️⃣ 4바이트 문자 (이모지, 고유문자 등)
형식: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
예: 🐱
(U+1F431
→ 0xF0 0x9F 0x90 0xB1
)
첫 바이트가 11110
으로 시작하면 4바이트 사용
3. 8비트(1바이트)만 있는데 어떻게 4바이트까지 확장 가능할까?
첫 번째 바이트가 몇 바이트를 사용할지를 정해주고,
나머지 바이트는 앞쪽에 10
비트 패턴을 사용해 연결됨.
즉, 각 바이트는 여전히 8비트지만, 여러 개를 합쳐서 32비트까지 확장하는 것!
🛠️ 예제 분석
유니코드 코드 포인트: U+0041
(10진수 65
)
2진수 표현: 00000000 01000001
(16비트)
UTF-8 인코딩: 01000001
(1바이트, 아스키와 동일)
➡ 첫 바이트가 0xxxxxxx
이므로 1바이트만 사용되며, 그대로 저장
➡ 즉, A는 아스키 코드 그대로 저장됨 (0x41
)
유니코드 코드 포인트: U+03C0
(10진수 960
)
유니코드 값이 0x0080~0x07FF
사이이므로, 2바이트를 사용해야 함
2진수 표현: 00000011 11000000
(16비트)
UTF-8 인코딩: 11001111 10000000
(2바이트 사용)
첫 번째 바이트: 110xxxxx
(MSB 3비트를 110
으로 설정)
두 번째 바이트: 10xxxxxx
(MSB 2비트를 10
으로 설정)
실제로 저장되는 데이터:
이렇게 하면 11비트(5비트 + 6비트)를 모두 저장할 수 있음.
유니코드: U+AC00
(10진수 44032
)
2진수 표현: 10101100 00000000
(16비트)
U+AC00
는 0x0800~0xFFFF 범위에 속하므로 3바이트 사용
첫 번째 바이트: 1110xxxx (MSB 4비트를 1110
으로 설정) ➡ 첫 바이트가 1110xxxx
이므로 3바이트 사용
두 번째 바이트: 10xxxxxx (MSB 2비트를 10
으로 설정)
세 번째 바이트: 10xxxxxx (MSB 2비트를 10
으로 설정)
첫 번째 바이트: 11101010
(4비트 데이터 포함)
두 번째 바이트: 10110000
(6비트 데이터 포함)
세 번째 바이트: 10000000
(6비트 데이터 포함)
➡ 총 16비트(4비트 + 6비트 + 6비트)를 저장 ➡ UTF-8에서 한글 1자는 3바이트를 차지함
유니코드: U+1F431
(10진수 128049
)
2진수 표현: 00011111 01000011 00010001
(21비트)
U+1F431
는 0x10000~0x10FFFF 범위에 속하므로 4바이트 사용
첫 번째 바이트: 11110xxx (MSB 5비트를 11110
으로 설정) ➡ 첫 바이트가 11110xxx
이므로 4바이트 사용
두 번째 바이트: 10xxxxxx (MSB 2비트를 10
으로 설정)
세 번째 바이트: 10xxxxxx (MSB 2비트를 10
으로 설정)
네 번째 바이트: 10xxxxxx (MSB 2비트를 10
으로 설정)
첫 번째 바이트: 11110000
(3비트 데이터 포함)
두 번째 바이트: 10011111
(6비트 데이터 포함)
세 번째 바이트: 10010000
(6비트 데이터 포함)
네 번째 바이트: 10110001
(6비트 데이터 포함)
➡ 총 21비트(3비트 + 6비트 + 6비트 + 6비트)를 저장 ➡ UTF-8에서 이모지는 4바이트를 차지함
결론
UTF-8은 첫 번째 바이트의 앞쪽 비트 패턴을 사용해 바이트 수를 결정
모든 문자는 최소 1바이트~최대 4바이트로 동적 확장 가능
각 바이트는 여전히 8비트지만, 여러 개를 연결하여 32비트까지 표현 가능!
UTF-8에서 아스키 문자를 처리하는 방식을 우체국(ASCII)과 국제 택배(UTF-8)의 차이로 이해해볼 수 있어요.
📌 우체국(ASCII)
미국(ASCII)에서 편지를 보낼 때는 그냥 영어 주소만 적으면 끝!
간단하고 빠름 → 한글, 일본어, 중국어 등 복잡한 언어가 없음.
📌 국제 택배(UTF-8)
한국에서 외국으로 택배를 보낼 때(유니코드 문자 사용) → 추가적인 스티커(인코딩)가 필요함!
예를 들어, 한글 ‘가’(U+AC00)를 국제 배송하려면 3바이트(UTF-8)로 변환해야 함.
반면, 영어(A~Z, 숫자 0~9)는 그냥 그대로 전달 가능!
즉, 영어(아스키)는 그대로 사용 가능하지만, 한글, 중국어, 이모지는 추가적인 포장(UTF-8 인코딩)이 필요!
컴퓨터 간 통신에서는 제어 문자와 7비트 환경 때문에 8비트 데이터를 직접 주고받기 어려운 문제가 있었다. (7비트 환경에서 8비트 데이터를 전송하기 위한 인코딩 기법)
이를 해결하기 위해 Quoted-Printable (QP) 인코딩과 Base64 인코딩이 등장했다.
용도: 7비트만 지원하는 통신 환경에서도 8비트 데이터를 송수신할 수 있도록 하는 인코딩
주로 사용된 곳: 전자우편(Email) 첨부 파일 전송
출력 가능한 ASCII 문자만 사용
8비트 값을 표현할 때=
뒤에 바이트의 각 니블을 표현하는 16진수 2자리로 변환
예: =
문자를 그대로 사용할 수 없으므로 =3D
로 변환
위의 8비트 표현 인해 =
가 특별한 의미를 지니기 때문인 이유
줄 끝의 공백(space)과 탭(tab)도 =20
, =09
로 변환해야 함
한 줄은 최대 76자까지 제한됨
hello=world
hello=3Dworld
Tab 공백
Tab=09공백=20
➡ =
기호를 이용해 16진수 값으로 변환하는 것이 특징
잘 작동은 하나, 1바이트를 표현하기 위해 3바이트를 사용하기 때문에 아주 비효율적이다
이 뜻은 QP 인코딩에서 "1바이트를 3바이트로 변환한다"는 말은, 특정 8비트 문자를 =XX
형식으로 변환한다는 뜻이다.
QP 인코딩의 핵심 원리
아스키(ASCII)에서 안전한 문자는 그대로 유지.
예: Hello, world!
→ 그대로 유지됨.
특수 문자(8비트 문자 또는 공백 등)는 =XX(16진수)로 변환.
예: 공백(ASCII 32) → =20
예: é
(ASCII 233) → =E9
예: ü
(ASCII 252) → =FC
줄 끝에 공백이 있으면 =XX
로 변환해야 함.
Hello
→ Hello=20
Hello.
→ Hello.=20
"1바이트를 표현하기 위해 3바이트를 사용한다"는 의미
특정 문자는 =XX(16진수) 형태로 표현되는데, 이때 원래 1바이트였던 문자가 3바이트로 변함.
예: é
(1바이트) → =E9
(3바이트)
예: 공백(1바이트) → =20
(3바이트)
예: ü
(1바이트) → =FC
(3바이트)
👉 즉, 원래 1바이트였던 문자가 =
와 두 개의 16진수(2글자)를 포함하면서 3바이트로 늘어나는 것!
예제 비교
➡ QP 인코딩 결과 (그대로 유지됨)
✅ 공백이나 특수 문자가 없어서 원본 크기 유지됨.
➡ QP 인코딩 결과 (=20
추가됨)
🚨 공백(
)이 =20
(3바이트)로 변환됨.
"1바이트가 3바이트로 늘어나는 경우" 정리
공백(Space)
32
=20
é
233
=E9
ü
252
=FC
á
225
=E1
📌 즉, QP 인코딩은 8비트 문자를 7비트 환경에서도 사용할 수 있도록 변환하지만, 비효율적으로 크기가 증가함.
지금보다 컴퓨터 사이의 통신 속도가 훨 씬 느렸던 과거에는 이런 효율성이 정말 중요했다. 그래서 Base64 인코딩이 효율적으로 필요했음
Base64 인코딩은 3바이트 데이터를 4문자로 표현한다. 3바이트 데이터의 24비트 -> 4가지 6비트 -> 각 덩어리 6비트에 출력 가능한 문자 할당
용도: 8비트 바이너리 데이터를 안전하게 ASCII 문자로 변환
주로 사용된 곳:
이메일 첨부 파일 전송 (MIME)
이미지, 동영상 인코딩
JWT(JSON Web Token) 토큰 인코딩
URL-safe 데이터 인코딩
3바이트(24비트) 데이터를 4개의 6비트 블록으로 변환
각 6비트 블록을 Base64 문자 테이블에 매핑
매핑된 문자는 A-Z, a-z, 0-9, +
, /
(총 64개 문자 사용)
원본 데이터 길이가 3바이트의 배수가 아닐 경우 패딩(=
) 추가
0
A
16
Q
32
g
48
w
1
B
17
R
33
h
49
x
2
C
18
S
34
i
50
y
3
D
19
T
35
j
51
z
4
E
20
U
36
k
52
0
5
F
21
V
37
l
53
1
6
G
22
W
38
m
54
2
7
H
23
X
39
n
55
3
8
I
24
Y
40
o
56
4
9
J
25
Z
41
p
57
5
10
K
26
a
42
q
58
6
11
L
27
b
43
r
59
7
12
M
28
c
44
s
60
8
13
N
29
d
45
t
61
9
14
O
30
e
46
u
62
+
15
P
31
f
47
v
63
/
🔹 예제: 숫자 0, 1, 2
의 Base64 변환
입력 값 (10진수):
이진수 변환:
6비트씩 나누기:
Base64 문자 매핑:
➡ Base64 인코딩 결과: AAEC
Base64 패딩 (Padding)
Base64는 3바이트(24비트)
씩 데이터를 변환하는데, 원본 데이터가 3바이트의 배수가 아닐 경우 패딩(=
)을 사용하여 길이를 맞춘다.
hi
(2바이트)
aGk=
A
(1바이트)
QQ==
cat
(3바이트)
Y2F0
➡ 2바이트 남으면 =
추가, 1바이트 남으면 ==
추가
QP 인코딩
이메일 전송
=
기호 사용, 16진수 변환
hello=3Dworld
Base64 인코딩
첨부 파일, JWT, URL 인코딩
3바이트 → 4문자, 패딩(=
) 사용
c2VjcmV0
(Base64로 "secret")
QP 인코딩은 문자 데이터를 손상 없이 송수신할 때 사용
Base64 인코딩은 바이너리 데이터를 ASCII로 안전하게 변환할 때 사용 🚀
URL에서 인코딩이 필요한 이유
웹에서 URL(Uniform Resource Locator)
은 리소스를 찾기 위한 주소 역할을 한다.
하지만 특정 문자는 URL에서 특별한 의미를 가지므로, 문자 그대로(literal) 사용하기 어렵다.
🔹 예를 들어:
슬래시(/) → URL에서 디렉터리 구분자로 사용됨.
공백( ) → URL에서 허용되지 않음.
& (앰퍼샌드) → 쿼리 파라미터 구분자로 사용됨.
% (퍼센트 기호) → URL 인코딩에서 특별한 의미를 가짐.
👉 따라서, 특별한 의미가 있는 문자를 문자 그대로(literal) 표현할 방법이 필요하다! 👉 이를 위해 퍼센트 인코딩(Percent-Encoding, URL Encoding) 방식을 사용한다.
퍼센트 인코딩(Percent-Encoding) 원리
각 문자를 16진수(헥사 코드)로 변환한 후, 앞에 %
를 붙이는 방식으로 인코딩.
형식: %XX
(XX는 해당 문자의 16진수 값)
🔹 예제:
공백(Space)
32
20
%20
슬래시(/)
47
2F
%2F
퍼센트(%)
37
25
%25
앰퍼샌드(&)
38
26
%26
등호(=)
61
3D
%3D
퍼센트 인코딩 예제
🔹 일반적인 URL
공백(Space)은 허용되지 않음!
🔹 퍼센트 인코딩된 URL
✅ 공백( ) → %20
으로 인코딩되어 문제없이 사용 가능!
퍼센트 인코딩이 필요한 상황
(1) URL에 공백이 포함될 때 : 공백은 URL에서 사용할 수 없음!
(2) URL에 /
(슬래시)를 그대로 사용하고 싶을 때
여기서는 /
가 디렉터리 구분자로 사용되므로, 특별한 의미가 있음. 하지만 만약 /
를 일반 문자로 사용하고 싶다면?
이렇게 하면 /
가 디렉터리 구분자가 아니라 일반 문자로 해석됨!
(3) URL에 &
(앰퍼샌드) 포함될 때
여기서 &
는 파라미터 구분자로 사용됨. 하지만 &
를 문자 그대로 표현하고 싶다면?
✅ %26
로 인코딩하면 &
를 일반 문자로 인식!
퍼센트 인코딩이 적용되는 곳
웹 브라우저 (주소창 자동 인코딩)
HTTP 요청 (GET, POST 데이터 전송 시)
HTML 폼 데이터 전송 (application/x-www-form-urlencoded)
REST API 요청 시 특수 문자 처리
QR 코드 URL 인코딩
퍼센트 인코딩을 처리하는 코드 예제
Python에서 URL 인코딩하는 방법
JavaScript에서 URL 인코딩하는 방법
퍼센트 인코딩(Percent-Encoding, URL Encoding)은 특수 문자를 URL에서 안전하게 표현하는 방법이다.
형식: %XX
(XX는 해당 문자의 16진수 값)
공백( )은 %20
, 슬래시(/)는 %2F
, 퍼센트(%)는 %25
등으로 변환됨.
웹 브라우저, API 요청, HTML 폼 데이터 전송 등에 사용됨.
Python의 urllib.parse.quote()
나 JavaScript의 encodeURIComponent()
를 사용하여 자동 변환 가능!
1️⃣픽셀과 RGB 색 모델
컴퓨터 그래픽스에서는 픽셀(Pixel, Picture Element)
을 사용하여 이미지를 표현한다.
픽셀은 색을 가지며, 컴퓨터 모니터는 빨간색(R), 녹색(G), 파란색(B)을 조합하여 색을 만든다.
이러한 색 표현 방식을 RGB 색 모델(RGB Color Model)이라고 한다.
(0,0,0) → 검은색 (모든 빛이 꺼진 상태)
(1,1,1) → 흰색 (모든 빛이 최대로 켜진 상태)
(1,0,0) → 빨간색
(0,1,0) → 녹색
(0,0,1) → 파란색
(1,1,0) → 노란색 (빨강 + 녹색)
(0,1,1) → 청록색(Cyan) (녹색 + 파랑)
(1,0,1) → 자홍색(Magenta) (빨강 + 파랑)
(0.5, 0.5, 0.5) → 회색 (빛의 강도를 낮춤)
이처럼 RGB 모델에서는 빛을 추가하여 색을 표현하는데, 이를 가산 색 시스템(Additive Color System)이라고 한다.
2️⃣ 가산 색 시스템 vs 감산 색 시스템
가산 색 시스템(Additive Color System)
RGB 모델처럼 빛을 섞어 색을 표현하는 방식.
컴퓨터 모니터, TV, 프로젝터 등에 사용됨.
빛을 더할수록 밝아지고, 모두 더하면 흰색이 됨.
감산 색 시스템(Subtractive Color System)
청록색(Cyan), 자홍색(Magenta), 노란색(Yellow)을 기본 색으로 사용.
인쇄물, 프린터에서 사용되며, 흰 배경에서 특정 색을 제거하여 원하는 색을 표현.
색을 섞을수록 어두워지고, 모두 더하면 검은색이 됨.
3️⃣ 컴퓨터에서 색 표현 (RGB & 24비트 컬러)
RGB 24비트 색 표현 방식
빨간색(R): 8비트 (0~255)
녹색(G): 8비트 (0~255)
파란색(B): 8비트 (0~255)
예: 순수 빨강 (255, 0, 0) → 11111111 00000000 00000000 (16진수: 0xFF0000)
색 표현 예시
검정
(0, 0, 0)
#000000
흰색
(255, 255, 255)
#FFFFFF
빨강
(255, 0, 0)
#FF0000
녹색
(0, 255, 0)
#00FF00
파랑
(0, 0, 255)
#0000FF
4️⃣ 32비트 컬러와 투명도(알파 채널)
24비트 컬러에서는 RGB 값만 사용하지만, 컴퓨터에서는 32비트(4바이트)로 색을 저장하기도 한다.
현대 컴퓨터들이 24비트 단 위로 계산을 수행하도록 설계되지 않았기 때문이다. 그 결과 그림 1-20처럼 24비트에 가장 가까운 표준 크기인 32비트(워드)에 색을 넣어서 처리하곤 한다
추가된 8비트는 "알파 채널(Alpha Channel)"로 사용되며, 투명도(Opacity)를 결정함.
미사용인 부분을 사용하기 위한 8비트
📌 32비트 색 표현 방식 (RGBA)
알파(투명도)
빨강 (R)
녹색 (G)
파랑 (B)
예: 반투명 빨강 (128, 255, 0, 0) → 10000000 11111111 00000000 00000000 (16진수: 0x80FF0000)
✅ 알파(Alpha) 값의 의미
0 → 완전 투명 (보이지 않음)
128 → 반투명 (50% 불투명)
255 → 완전 불투명 (기본 RGB 색상)
5️⃣ 인간의 눈과 색상 인식
컴퓨터가 색을 수학적으로 표현하는 반면, 인간의 눈은 비선형적으로 색을 인식한다.
인간의 눈은 녹색(G)에 가장 민감하고, 파란색(B)에는 둔감하다.
따라서 디지털 영상 기술에서는 감마 보정(Gamma Correction)을 적용하여, 인간의 눈이 더 자연스럽게 색을 인식하도록 한다.
6️⃣ 결론
컴퓨터는 RGB 색 모델을 사용하여 색을 표현함.
RGB 모델은 가산 색 시스템(빛을 더하는 방식)이며, 프린터는 감산 색 시스템(색을 빼는 방식)을 사용함.
현대 컴퓨터는 24비트 컬러(8비트씩 R, G, B)를 사용하여 1677만 개의 색을 표현 가능.
32비트 컬러에서는 알파(투명도) 값을 추가하여 RGBA 포맷으로 저장함.
인간의 눈은 선형적으로 색을 인식하지 않으며, 감마 보정이 필요함.
전통 애니메이션과 투명도의 필요성
초기 애니메이션에서는 각 프레임을 손으로 직접 그림 → 배경을 정확히 재생산하기 어려워 화면이 흔들리는 문제(jitter) 발생.
📌 셀 애니메이션(Cell Animation)의 등장
1915년, 존 브레이(John Bray) & 얼 허드(Earl Hurd)가 셀 애니메이션을 발명.
캐릭터를 투명한 필름(셀룰로이드) 위에 그려서 고정된 배경 위에서 움직이는 방식.
배경을 다시 그릴 필요가 없어짐 → 작업량 감소 & 일관된 프레임 유지 가능.
📌 컴퓨터 애니메이션의 발전
1970~80년대, 컴퓨터 애니메이션 기술 발전.
하지만 당시 컴퓨터 성능이 부족 → 각각의 요소를 개별적으로 그린 후 합성(composite) 필요.
셀 애니메이션처럼 투명도를 이용한 이미지 합성 기법 개발됨.
알파 채널(Alpha Channel)과 투명도
📌 알파 채널의 개념
1984년, 톰 더프(Tom Duff) & 토머스 포터(Thomas Porter)가 컴퓨터 그래픽에서 투명도를 수학적으로 정의.
각 픽셀에 알파(α) 값을 추가하여 투명도를 결정.
α = 0 → 완전 투명
α = 1 (255) → 완전 불투명
0 < α < 1 → 반투명 (예: 50% 투명 = α 127)
📌 알파 블렌딩(Alpha Blending)
색상(RGB) 값에 알파(α)를 곱하여 저장.
예제:
빨강(R) = 200, 녹색(G) = 0, 파랑(B) = 0
α = 1.0 (완전 불투명) → 저장된 값: (200, 0, 0, 255)
α = 0.5 (50% 투명) → 저장된 값: (100, 0, 0, 127)
미리 RGB 값에 α를 곱해 저장하면, 픽셀을 사용할 때마다 추가적인 연산이 필요하지 않음.
📌 RGBA(32비트) 색 표현
채널
알파(α)
빨강(R)
녹색(G)
파랑(B)
색상 표현 방식 (웹과 그래픽)
웹에서는 색을 16진수(Hex)로 표현하는 Hex Triplet 방식 사용.
📌 색상 표현 형식
#RRGGBB
→ R, G, B는 각각 16진수(00~FF)
예제:
#FF0000
→ 빨강
#00FF00
→ 초록
#0000FF
→ 파랑
#FFFF00
→ 노랑 (빨강+초록)
#FFFFFF
→ 흰색
#000000
→ 검정
📌 알파 채널이 포함된 색상 (RGBA) 웹에서는 알파 값을 별도로 추가할 수 있음.
rgba(255, 0, 0, 0.5)
→ 반투명 빨강 (50%)
CSS에서는 opacity: 0.5;
같은 속성을 활용.
웹 페이지는 주로 UTF-8 문자로 구성된 텍스트 기반의 문서이기 때문에, 웹에서 색을 사용하려면 텍스트로 색을 표현하는 방법이 필요하다.
웹에서는 16진수(Hexadecimal) 코드를 사용해 색을 표현하는 방식이 표준.
📌 Hex 색상 코드 형식
RR
: 빨간색(R) (Red, 00~FF)
GG
: 녹색(G) (Green, 00~FF)
BB
: 파란색(B) (Blue, 00~FF)
📌 예제
검정
#000000
(R=0, G=0, B=0)
흰색
#FFFFFF
(R=255, G=255, B=255)
빨강
#FF0000
(R=255, G=0, B=0)
초록
#00FF00
(R=0, G=255, B=0)
파랑
#0000FF
(R=0, G=0, B=255)
노랑
#FFFF00
(R=255, G=255, B=0)
회색
#808080
(R=128, G=128, B=128)
📌 설명
#FF0000
→ 빨강 (Red 255, Green 0, Blue 0)
#00FF00
→ 초록 (Red 0, Green 255, Blue 0)
#0000FF
→ 파랑 (Red 0, Green 0, Blue 255)
#FFFF00
→ 노랑 (빨강+초록)
#000000
→ 검정 (모든 값이 0)
#FFFFFF
→ 흰색 (모든 값이 최대)
투명도(α)와 RGBA 표현
Hex 코드에는 투명도(α, 알파 채널) 정보가 없음. 웹에서는 RGBA 형식을 사용해 투명도를 포함할 수 있음.
📌 RGBA 색상 코드 형식
R
: 빨강 (0~255)
G
: 녹색 (0~255)
B
: 파랑 (0~255)
A
: 알파(투명도) (0.0 ~ 1.0)
0.0 → 완전 투명
1.0 → 완전 불투명
📌 예제
Hex 코드
#FF5733
6자리 16진수 (투명도 없음)
RGBA 코드
rgba(255, 87, 51, 0.8)
10진수 RGB + 투명도(0~1)
✅ 웹에서 색상은 Hex 코드 (#RRGGBB
)로 표현하며, 16진수로 색을 지정할 수 있음.
✅ 투명도(α)가 필요할 경우 RGBA(rgba(R,G,B,A)
) 형식을 사용해야 함.
✅ Hex 코드만으로는 투명도를 설정할 수 없기 때문에 CSS에서는 opacity
또는 rgba()
를 활용함.
2장에서는 컴퓨터를 이루는 물리적 구성요소를 이해하고 왜 컴퓨터가 애초부터 비트를 사용 하는지 이해할 때 도움이 될 수 있도록 하드웨어의 기본을 배운다
📌 RGB 색 모델의 원리