Unfixable: 고칠 수 없는 것들 #1 — Excel

엑셀에 SEPT1을 입력하면 9월 1일이 된다. =DATE(1900, 2, 29)를 치면 존재하지 않는 날짜가 나온다. 0.1 + 0.2는 0.3이 아니다.
전 세계에서 가장 많이 쓰이는 소프트웨어가 이렇게 이상하게 동작한다. 왜 고치지 않는 걸까?
TL;DR
- 엑셀은 유전자 이름을 날짜로 바꿔서 과학 논문의 20-31%를 오염시켰다. 결국 유전자 이름이 바뀌었다.
- 1900년 윤년 버그는 Lotus 1-2-3 호환성을 위해 의도적으로 복사한 것이다.
- 타입 변환 규칙이 일관성 없다.
TRUE + 1은2지만TRUE = 1은FALSE다. VLOOKUP의 기본값은 유사 일치다. 정렬 안 된 데이터에 쓰면 조용히 틀린 값을 준다.- 부동 소수점 오차를 화면에서 숨기는 전략 때문에 더 헷갈린다.
- 하위 호환성이 버그보다 중요하다. 수십억 개의 파일이 이 버그에 의존한다.
유전자 이름을 날짜로 바꾸는 엑셀
유전학자들은 SEPT1 (Septin 1), MARCH1 (Membrane Associated Ring-CH-Type Finger 1) 같은 유전자를 다룬다. 그런데 이 데이터를 엑셀에 넣는 순간, 9월 1일과 3월 1일이 된다.
| A (Gene Symbol) | B (Description) | |
|---|---|---|
| 1 | BRCA1 | Breast Cancer 1 |
| 2 | 9월 1일 | Septin 1 |
| 3 | 3월 1일 | Membrane Associated Finger 1 |
| 4 | 10월 4일 | POU Class 5 Homeobox 1 |
| 5 | TP53 | Tumor Protein p53 |
우측 상단의 "Open in Excel" 버튼을 클릭하여 유전자 이름이 어떻게 날짜로 변하는지 관찰해 보세요.
2016년 Genome Biology 연구에서 유전자 목록이 포함된 보충 자료를 조사했다. 약 20%에서 오류가 발견됐다. 2021년 PLOS Computational Biology는 10,000개 이상의 논문을 전수 조사했다. 31%(11,117개 중 3,436개)로 증가했다.
결국 인간 유전자 명칭 위원회(HGNC)가 항복했다. 2020년, 엑셀이 날짜로 오인할 만한 유전자 이름을 아예 바꾸는 가이드라인을 발표했다. 소프트웨어 버그 때문에 인류의 과학적 자산인 유전자 명칭을 변경한 역사적 사건이다.
변경된 주요 유전자들:
SEPT1→SEPTIN1MARCH1→MARCHF1OCT4→POU5F1WARS(tRNA synthetase) →WARS1
마이크로소프트는 2023년 10월에야 자동 변환을 끌 수 있는 옵션을 추가했다. 하지만 이미 수천 편의 논문 데이터는 오염된 후였다.
1900년 2월 29일이 존재한다
엑셀에서 =DATE(1900, 2, 29)를 입력하면 유효한 날짜가 나온다. 하지만 실제 역사에서 1900년은 윤년이 아니었다.
| A (Input Date) | B (Actual) | C (Excel Stored) | |
|---|---|---|---|
| 1 | 1900-02-28 | 화요일 | 화요일 (SN:59) |
| 2 | 1900-02-29 | 존재하지 않음 | 수요일 (SN:60) |
| 3 | 1900-03-01 | 목요일 | 목요일 (SN:61) |
각 행을 클릭해 보세요. 엑셀은 1900-01-01부터의 일수를 정수(Serial Number)로 관리합니다. 60번을 결번으로 처리할 수 없기에 실제로는 없는 2월 29일을 가상으로 유지하고 있습니다.
그레고리력의 규칙은 간단하다. 100의 배수이면서 400의 배수가 아니면 평년이다. 1900년은 평년이어야 한다. 왜 엑셀은 이런 기초적인 오류를 범하는 걸까?
의도된 버그
시작은 1980년대 초반의 Lotus 1-2-3였다. 당시 절대 강자였던 Lotus 1-2-3가 1900년을 윤년으로 잘못 계산했다. 마이크로소프트는 엑셀을 출시하면서 사용자들이 Lotus에서 쉽게 넘어올 수 있도록 똑같은 버그를 그대로 복사했다. "Bug-for-bug compatibility"다.
마이크로소프트가 이 버그를 고치지 않는 이유는 명확하다.
데이터 오염: 버그를 수정하면 기존의 모든 엑셀 파일 속 날짜가 하루씩 앞당겨진다. 데이터 무결성이 깨진다.
함수 결과 변화: WEEKDAY() 같은 요일 계산 함수가 기존과 다른 값을 반환한다. 수많은 수식이 오작동한다.
타 프로그램 호환성: 엑셀의 시리얼 날짜 시스템을 공유하는 다른 소프트웨어와의 연결이 끊어진다.
재밌는 점이 있다. 초기 Mac용 엑셀은 이 문제를 피하기 위해 1904년 날짜 시스템을 기본값으로 사용했다. 이로 인해 윈도우와 Mac 간에 파일을 주고받을 때 날짜가 1,462일(1900년 1월 1일과 1904년 1월 1일 사이의 일수) 차이가 나는 기괴한 현상이 발생했다.
타입 변환 규칙이 일관성 없다
엑셀은 데이터 타입을 암시적으로 변환한다. 그런데 그 기준이 상황마다 다르다.
열 C~F는 각 연산자의 결과를 실시간으로 보여줍니다. 같은 값이 연산자에 따라 어떻게 다르게 취급되는지 비교해 보세요.
산술 연산에서는 숫자: TRUE + 1은 2가 된다. TRUE를 1로 취급한다.
비교 연산에서는 엄격히 구분: 그런데 TRUE = 1은 FALSE를 반환한다. 비교 연산에서는 불리언과 숫자를 엄격히 구분한다.
문자열도 마찬가지: "12" > 12는 TRUE다. 엑셀의 비교 계층 구조에서 텍스트는 항상 숫자보다 크다.
이런 암시적 변환은 편리하지만, 예상치 못한 오류를 만든다.
문서화되지 않은 계층 구조
엑셀의 암시적 형 변환에는 내부적인 계층 구조가 있다. 비교 연산 시 다음 순서로 결정된다.
- 불리언 (Boolean): 가장 큼.
TRUE > "ZZZ"는TRUE다. - 텍스트 (String): 숫자보다 큼.
"1" > 9999는TRUE다. - 숫자 (Numeric) 및 날짜/시간: 가장 낮음.
TRUE > 9999999는 TRUE를 반환하지만, TRUE * 1은 불리언이 숫자로 변환되어 1이 된다.
| A (Input) | B (Calculation) | |
|---|---|---|
| 1 | 10 Edit | SUM40 |
| 2 | "20" Edit | PLUS#VALUE! |
| 3 | 30 Edit | -- |
A2 셀처럼 숫자 앞에 작은따옴표(')를 붙이면 텍스트로 인식됩니다. SUM은 이 셀을 무시하지만, + 연산자는 셀 참조 시 #VALUE! 에러를 발생시킵니다.
SUM() 함수와 + 연산자도 다르게 동작한다. SUM()은 범위 내의 텍스트를 무시한다. 하지만 + 연산자는 텍스트를 숫자로 변환하려고 시도하고, 실패하면 #VALUE! 에러를 낸다. 이런 미묘한 차이가 데이터 분석의 일관성을 해친다.
VLOOKUP의 위험한 기본값
VLOOKUP에는 치명적인 함정이 있다. 마지막 인자 range_lookup의 기본값이 TRUE(유사 일치)라는 점이다.
| A (ID) | B (Name) | D (Lookup) | E (Result) | |
|---|---|---|---|---|
| 1 | 101 | Apple | 103 | Cherry |
| 2 | 105 | Banana | - | - |
| 3 | 102 | Cherry | - | - |
| 4 | 104 | Dragonfruit | - | - |
| 5 | 103 | Elderberry | - | - |
103을 찾지만 정렬 안 된 데이터에서 102(Cherry)를 반환합니다. 실제 103은 5행에 있습니다!
range_lookup이 TRUE일 때 엑셀은 이진 탐색(Binary Search)을 사용한다. 빠르다. 하지만 데이터가 반드시 오름차순으로 정렬되어 있어야 한다는 전제가 필요하다.
데이터가 정렬되어 있지 않은데 마지막 인자를 생략하면? 엑셀은 찾고자 하는 값보다 큰 값을 만나는 순간 탐색을 멈춘다. 그리고 그 직전 행의 값을 조용히 반환한다. 에러가 나지 않는다. 사용자는 자신이 틀린 데이터를 보고 있다는 사실조차 모른다.
훗날 등장한 XLOOKUP은 이 문제를 해결했다. '정확히 일치'를 기본값으로 변경하고, 이진 탐색을 명시적으로 선택하게 만들었다.
0.1 + 0.2 ≠ 0.3
엑셀은 IEEE 754 표준의 64비트 부동 소수점 형식을 사용한다. 컴퓨터는 2진법을 쓰기 때문에 0.1이나 0.2 같은 10진수 소수를 정확하게 표현하지 못한다. 미세한 오차가 생긴다.
Select a cell to investigate
15자리의 한계: 엑셀은 최대 15자리까지만 정확도를 보장한다. 그 이상은 모두 0으로 변환된다. 신용카드 번호나 고유 식별자(ID)를 숫자로 입력하면 치명적인 데이터 손실이 발생한다.
오차를 숨기는 전략
엑셀은 사용자 친화성을 위해 미세한 오차를 화면상에서 숨긴다(Masking). 부동 소수점 오차를 사용자가 인지하지 못하게 하는 것이다.
표시 보정: 0.3000...04 같은 값을 0.3으로 반올림해서 화면에 표시한다.
일치 보정: A1=B1 같은 비교 연산 시 미세한 오차(특정 임계값 이내)를 무시하고 TRUE를 반환한다.
영점 보정: 연산 결과가 0에 아주 가까우면 강제로 0으로 취급한다.
| A | B | C (Paradox) | |
|---|---|---|---|
| 1 | 0.3 | 0.3 | ComparisonTRUE |
| 2 | Internal DiffFALSE |
하지만 이 보정은 수식이 복잡해질수록 한계를 드러낸다. A1=B1은 TRUE인데 A1-B1=0은 FALSE가 되는 기괴한 상황이 생긴다. 이를 피하려면 ROUND() 함수로 명시적으로 정밀도를 제어해야 한다.
그 외의 기묘한 동작들
엑셀에는 하위 호환성과 역사적 이유로 인해 숨겨진 기벽들이 더 있다.
비공개 함수 DATEDIF: 두 날짜 사이의 간격을 계산하는 DATEDIF는 엑셀의 함수 목록이나 공식 문서 어디에도 없다. 오직 Lotus 1-2-3와의 호환성을 위해 비밀리에 유지되고 있다. 직접 수식을 입력해야만 쓸 수 있다.
태국어 전용 BAHTTEXT: 엑셀에는 숫자를 텍스트로 바꿔주는 기능이 거의 없다. 그런데 유독 태국 바트화로 변환해주는 BAHTTEXT 함수만은 전 세계 버전의 엑셀에 기본 탑재되어 있다.
에러 메시지의 불일치: VLOOKUP에서 컬럼 인덱스가 1보다 작으면 #VALUE! 에러를 낸다. 값을 찾지 못하면 #N/A를 낸다. 상황에 따라 에러의 종류가 바뀐다.
와일드카드의 차별: SUMIF나 COUNTIF 같은 함수에서는 *나 ? 같은 와일드카드를 지원한다. 하지만 일반적인 배열 수식에서는 이를 직접 지원하지 않는다. 별도의 논리 연산을 조합해야 한다.
엑셀이 초래한 현실 세계의 비극
이런 버그들은 단순한 해프닝이 아니다. 거대한 경제적, 사회적 손실을 가져왔다.
JP Morgan (2012): 모델링 스프레드시트의 수식 오류(단순 복사-붙여넣기 실수 등)로 약 60억 달러(약 8조 원) 이상의 거래 손실이 발생했다.
영국 공중보건국 (2020): 구형 엑셀 파일(.xls)의 행 제한(65,536행) 때문에 약 15,841명의 코로나19 확진자 데이터가 누락됐다. 밀접 접촉자 추적이 8일간 지연됐다.
하버드 경제학자들의 오류 (2010): 카르멘 라인하트와 케네스 로고프의 긴축 재정 정당화 논문에서 엑셀 수식 범위 지정 실수로 20개국 중 5개국 데이터가 누락됐다. 이는 전 세계적인 긴축 정책 기조에 영향을 미쳤다.
ID 무결성 손실: 17자리의 고유 ID를 엑셀로 불러오면 15자리 이후가 잘려나간다. 서로 다른 수백 명의 고객 ID가 동일한 값으로 합쳐지는 데이터 사고가 빈번하게 발생한다.
왜 고치지 않는가?
마이크로소프트의 최우선 가치는 '하위 호환성(Backward Compatibility)'이다.
"버그까지 똑같이 만들어야 사용자들이 안심하고 우리 제품을 쓴다."
엑셀 팀의 철학이다. 오늘날 엑셀 데이터를 기반으로 돌아가는 전 세계의 금융 시스템, 과학 데이터, 국가 행정망을 생각해보자. 사소한 버그 하나를 고치는 것이 가져올 파급력은 상상을 초월한다.
구글 시트(Google Sheets)조차 시장 점유율을 위해 엑셀의 유전자 이름 자동 변환 같은 버그를 똑같이 구현해야 했다. 반면 LibreOffice나 Gnumeric 같은 오픈소스 소프트웨어들은 이런 자동 변환을 구현하지 않아 유전학계에서 대안으로 꼽힌다.
엑셀은 단순한 소프트웨어가 아니다. 수십 년의 역사가 겹겹이 쌓인 거대한 생태계다. 그 안의 기묘한 동작들은 우리가 과거의 유산과 공존하며 살아간다는 증거다. 소프트웨어 공학에서 '호환성'이라는 가치가 얼마나 무겁고 무서운 것인지를 보여준다.