
해당 포스트는 CoreDumped의 "HOW COMPUTERS CAST STRINGS TO NUMBERS"를 보고 영감을 받아 작성한 글입니다.
우리는 코딩을 할 때 너무나 당연하게 문자열을 숫자로 변환합니다.
# Python
num = int("12345")
// C
int num = atoi("12345");
하지만 컴퓨터는 "12345"라는 문자열을 봤을 때, 이것이 수학적 의미의 1만 2천 3백 4십 5라는 것을 직관적으로 알지 못합니다. 컴퓨터 입장에서 문자열은 그저 문자들의 배열일 뿐입니다. 아래 사진과 같이 말이죠.

그렇다면 내부적으로 도대체 어떤 연산이 일어나길래 이 문자 덩어리가 산술 연산이 가능한 숫자로 바뀌는 걸까요?
가장 먼저 이해해야 할 것은 '문자 0'과 '숫자 0'은 다르다는 점입니다. 컴퓨터는 모든 문자를 숫자로 매핑해서 저장하는데, 가장 표준적인 ASCII(아스키) 코드를 보면 다음과 같습니다.
| 문자 (Char) | ASCII 값 (Decimal) | 이진수 (Binary) |
|---|---|---|
'0' | 48 | 0011 0000 |
'1' | 49 | 0011 0001 |
| ... | ... | ... |
'9' | 57 | 0011 1001 |
만약 우리가 문자 '5'와 '3'을 더한다면 컴퓨터는 이렇게 계산합니다.
'5'(53) +'3'(51) = 104 ('h')
우리가 원하는 값인 8이 나오지 않습니다. 그래서 문자열을 숫자로 바꾸는 알고리즘의 첫 번째 핵심은 바로 ASCII 값 보정입니다.
어떻게 하면 ASCII값으로 정수 형태의 값을 얻을 수 있을까요?
핵심 공식:
'문자' - '0'
문자에서'0'(48)의 아스키 값을 빼버리면, 실제 정수 값을 얻을 수 있습니다.
'5'(53) -'0'(48) = 5 (Integer)'9'(57) -'0'(48) = 9 (Integer)
단일 문자가 아니라 "123" 같은 여러 자리 문자열은 어떻게 처리할까요? 여기서 두 번째 핵심인 자릿수 누적 알고리즘이 등장합니다.
우리가 "123"을 읽는 순서는 1 → 2 → 3입니다.
이전 값에 10을 곱해서 자릿수를 하나 올리고, 새로운 숫자를 더하는 방식을 반복합니다.
"1" 읽음: 0 * 10 + 1 = 1"2" 읽음: 1 * 10 + 2 = 12 (기존 1이 10의 자리가 됨)"3" 읽음: 12 * 10 + 3 = 123 (기존 12가 120이 됨)2진수로 설명하면 아래와 같죠
atoi)C언어의 표준 라이브러리 함수인 atoi(ASCII to Integer)는 이 로직을 가장 날것(Raw) 그대로 보여줍니다. glibc나 임베디드 커널의 atoi 구현을 단순화하면 아래와 같습니다.
int my_atoi(const char *str) {
int result = 0;
int sign = 1;
int i = 0;
// 1. 공백 건너뛰기
while (str[i] == ' ') i++;
// 2. 부호 확인 (+, -)
if (str[i] == '-' || str[i] == '+') {
if (str[i] == '-') sign = -1;
i++;
}
// 3. 숫자 변환 (핵심 로직)
while (str[i] >= '0' && str[i] <= '9') {
// 기존 값에 10을 곱하고, 현재 문자에서 '0'을 뺀 값을 더함
result = result * 10 + (str[i] - '0');
i++;
}
return result * sign;
}
str[i])나 포인터(ptr)로 한 글자씩 접근합니다.str[i] - '0': 위에서 설명한 ASCII 보정 로직이 그대로 들어갑니다.int는 크기(보통 4바이트)가 정해져 있어, 숫자가 너무 크면 오버플로우가 발생할 수 있습니다. (그래서 실무에선 strtol을 더 권장합니다.)int)Python은 C보다 훨씬 고수준 언어입니다. int("12345678901234567890") 처럼 엄청나게 큰 숫자도 오버플로우 없이 처리합니다. 어떻게 가능할까요?
Python(CPython)의 소스 코드 중 Objects/longobject.c를 살펴보면 그 비밀이 있습니다. Python의 int는 단순한 4바이트 메모리가 아니라 PyLongObject라는 구조체(객체) 입니다.
int() 변환 과정strip)하고 _ (언더스코어) 같은 구분자를 처리합니다.0x(16진수) 등이 있는지 확인하여 진법(Base)을 결정합니다.PyLong_FromString):long)를 넘어가면, 숫자를 여러 개의 큰 조각(Digit 배열)으로 나누어 저장합니다.실제 구현체 소스코드: https://github.com/python/cpython/blob/f4364a51c1a8ce682fe9e4e96c6aba9f1b590422/Objects/longobject.c#L3044
/* CPython 소스 코드 (Objects/longobject.c) 개념적 의사코드 */
PyObject *
PyLong_FromString(const char *str, char **pend, int base)
{
// ... (공백 제거 및 부호 처리 생략) ...
long x = 0;
// 작은 숫자일 경우: C언어와 똑같은 방식(Accumulator)으로 빠르게 처리
while (*str >= '0' && *str <= '9') {
long dig = *str - '0';
// 오버플로우 체크를 하며 10을 곱하고 더함
if ((x > limit) || (x * 10 > limit)) {
// 오버플로우 발생 시 'Big Integer' 처리 로직으로 전환
return PyLong_FromBigString(...);
}
x = x * 10 + dig;
str++;
}
// PyObject로 포장해서 반환
return PyLong_FromLong(x);
}
PyObject*)를 반환합니다."문자열을 숫자로 바꾼다"는 한 줄의 코드 뒤에는 다음과 같은 CS의 기초가 숨어 있었습니다.
x10을 반복한다.우리가 무심코 쓰는 함수 하나에도, 선배 개발자들의 치열한 고민과 최적화가 담겨 있습니다.