Python

1.1 + 0.1 == 1.2 가 False가 나오는 이유

gudwns5533 2026. 5. 25. 20:17

인간의 수학으로는 당연히 참이지만, 컴퓨터에게는 그렇지 않다. 그 이유는 컴퓨터가 소수를 다루는 방식에서 찾을 수 있다.

부동소수점(Floating Point)란 무엇인가?

 

부동소수점에서 '부동'은 움직이지 않는다는 뜻이 아니라, 소수점이 둥둥 떠다닌다(Floating)는 뜻이다.

아주 큰 수나 아주 작은 수를 제한된 비트(ex. 32비트, 64비트) 안에 효율적으로 표현하기 위해, 숫자를 고정된 형태의 과학적 표기법으로 나누어 저장하는 방식이다.

  • 부호 (Sign) : 양수 / 음수 결정
  • 가수 (Mantissa, M) : 숫자의 실제 유효 자릿수들을 저장
  • 지수 (Exponent, E) : 소수점의 위치를 결정

단정도(32비트)에서는 부호부 1비트, 지수부 8 비트, 가수부 23비트.

배정도(64비트)에서는 부호부 1비트, 지수부 11비트, 가수부 52비트로 표현한다.

💡고정 소수점이란?

  • 정수를 표현하는 비트 수와 소수를 표현하는 비트 수를 미리 정해 놓고 해당 비트만큼만 사용해서 숫자를 표현하는 방식
  • 정수를 표현하는 비트를 늘리면 큰 숫자를 표현할 수 있지만 정밀한 숫자를 표현하기는 힘들다. 그래서 소수를 표현하는 비트를 늘릴 경우 정밀한 숫자를 표현할 수 있지만 큰 숫자를 표현할 수 없다.

컴퓨터는 왜 10진수를 2진수로 변환해서 저장하는가?

 

컴퓨터의 근본적인 물리적 한계 때문이다. 컴퓨터의 반도체 트랜지스터는 전류가 '흐른다(On = 1)'와 '안 흐른다(Off = 0)'라는 두 가지 상태만 인식할 수 있다. 전압의 미세한 차이로 10가지 상태(0 ~ 9)를 구현하려고 하면 노이즈로 인한 불량률이 극도로 높아진다. 따라서 가장 안전하고 명확한 2진법을 채택하고, 모든 10진수 데이터는 입력 즉시 2진수로 변환하게 된다.

왜 일부 10진수는 2진수로 정확하게 표현되지 않는가?

 

우리가 10진수에서 1/3을 소수로 표현하면 0.33333333... 같은 무한소수가 되는 것처럼, 2진수 세계에서도 10진수 소수 중 일부는 무한소수가 된다.

10진수 소수가 유한소수가 되려면 분모의 소인수가 2와 5로만 이루어져야 한다. 반면 2진수 소수가 유한소수가 되려면 분모가 2^n 형태여야 한다.

  • 0.5는 1/2이므로 2진수로 0.1로 딱 떨어진다.
  • 하지만 0.1은 분모가 10 (2x5)이므로 2진수로 바꾸면 0.00011001100...과 같이 1100이 무한히 반복되는 무한소수가 된다.

컴퓨터 메모리는 무한하지 않기 때문에, 특정 자릿수에서 이 뒷부분을 강제로 잘라내고 반올림하여 저장한다. 이때 미세한 값의 오차가 발생하게 된다.

  • 1.1 → 1.00011001100110011001101... (미세한 오차 포함)
  • 0.1 → 0.00011001100110011001100... (미세한 오차 포함)

따라서 이때 1.1과 0.1을 더하면 1.2라고 인간은 생각하지만, 실제 컴퓨터 내부에서는 1.20000000000000177... 과 같은 미세한 오차가 발생해서 1.2와 같지 않다고 판단하는 것이다.

IEEE 754 표준이란 무엇인가?

 

과거 컴퓨터 제조사마다 소수를 저장하는 방식이 제각각이어서 프로그램이 컴퓨터마다 다르게 동작하는 혼란이 있었다.

이를 해결하기 위해 1975년 IEEE(전기전자공학자협회)에서 정립한 부동소수점 표현 표준 규격이 바로 IEEE 753이다.

현재 거의 모든 CPU와 프로그래밍 언어(Python, C, Java, JS 등)가 이 표준을 따른다. 주로 32비트(단정도)와 64비트(배정도) 형식을 사용한다.

부동소수점 오차는 실제 개발에서 어떤 문제를 발생시키는가?

 

눈에 보이지 않던 미세한 오차들이 쌓이고 반복되다보면 반복문, 비즈니스 로직, 시스템 간 통신에서는 프로그램이 멈추거나, 돈이 증발하거나, 재난을 일으키는 치명적인 버그로 돌변하게 된다.

 

1. 반복문이 끝나지 않는 '무한 루프' 지옥

"특정 조건이 될 때까지 이 작업을 반복해라"라는 코드를 작성한다고 생각해 보자. 이때 부동소수점을 기준으로 삼으면 프로그램이 영원히 멈추지 않는 지옥에 빠질 수 있다.

 

📍예) 게임에서 캐릭터의 독 데미지 시스템

캐릭터의 체력이 1, 캐릭터가 독에 걸려 1초에 체력이 0.1씩 깎이고, 체력이 0이 되면 캐릭터가 사망한다는 조건.

hp = 1.0
while hp != 0.0:
    hp -= 0.1
  • 인간의 생각 : 0.1씩 10번 빼서 체력이 0이되면서 반복문이 끝난다.
  • 컴퓨터의 현실 : 0.1은 무한소수이므로 0.1을 계속 빼다 보면 내부 오차가 누적되어 체력이 0.0000000000000000002 같은 상태가 되고, 다음엔 0을 건너뛰어 -0.0999999999999999998(음수)로 넘어가 버리게 된다.

  • 결과 : hp == 0.0 이라는 조건을 영원히 만족하지 못하므로, 컴퓨터는 계속해서 이 계산을 반복하게 되고 결국엔 다운된다.

2. 눈덩이처럼 불어나는 '누적 오차'

10^-16(0.0000000000000001)만큼의 아주 미세한 오차는 한두 번 계산할 때는 티가 나지 않는다. 하지만 시스템이 오랜 시간 켜져 있으면서 이 계산을 수백만 번 반복하면 오차가 1초, 2초 단위로 불어나게 된다.

 

📍패트리어트 미사일의 비극 (1991년)

걸프전 당시 미국의 패트리어트 미사일 기지가 날아오는 이라크의 스커드 미사일을 요격하지 못해 28명의 군인이 사망하는 참사가 있었다.

  • 원인 : 미사일 내부 컴퓨터는 시간을 계산할 때 0.1초 단위를 사용했다. 이 0.1을 2진수 부동소수점으로 계산하면서 아주 미세한 오차가 발생.
  • 결과 : 이 요격 시스템은 꺼지지 않고 100시간 동안 연속 가동 중이었음. 100시간 동안 0.1초마다 발생한 미세한 오차가 계속 더해져서 결국 약 0.34초 시간 오차가 누적되었음. 음속으로 날아오는 미사일에게 0.34초는 600미터가 넘는 오차거리를 발생시켰고, 결국 대참사로 이어짐.

3. 전자상거래(이커머스) 결제 금액 미스매치

쇼핑몰 앱을 개발할 때 세금(VAT), 할인율(%), 환율 등을 계산하다 보면 소수점 연산이 무조건 들어가게 된다. 이때 결제 데이터베이스와 정산 시스템이 꼬이게 된다.

 

📍예) 10% 추가 할인 이벤트

쇼핑몰에서 상품 3개를 묶어서 산 고객에게 각각 10%씩 할인해 주는 로직을 작성.

  • 상품 A, B, C의 가격은 각각 1,000원. 10% 할인이면 각각 100원씩 할인.
  • 내부적으로 환율이나 마일리지 비율(0.1)을 곱하다가 부동소수점 오차가 발생해 내부적으로 상품가격이 899.99999999...원으로 계산
  • 시스템은 원화(KRW) 계산을 위해 소수점을 버림 처리하여 데이터베이스에 899원으로 저장.
  • 결과 : 원래는 총합 2,700원이 결제되어야 하는데, 정산 시스템을 돌려보니 3원이 빈 2,697원이 결제. 하루에 동일한 제품으로 수백만 건의 거래가 일어나는 대형 쇼핑몰이라면 수만원이 비어 회계사고로 이어짐.

4. 웹 서핑 중 화면이 찢어지거나 요소가 뒤틀리는 현상

우리가 매일 쓰는 웹사이트나 스마트폰 UI를 개발할 때도 부동소수점이 발목을 잡는다. 화면의 레이아웃 비율(%)로 나눌 때 주로 발생하게 된다.

 

📍예) 3단 분할 배너 만들기

웹 디자이너가 홈페이지 메인 화면에 가로 크기가 똑같은 배너 3개를 나란히 배치해 달라고 요청. 개발자는 전체 화면(100%)을 3으로 나눈 33.333333...%를 각 배너의 너비로 설정.

  • 문제 발생 : 사용자가 모니터 해상도가 가로 1920픽셀이라고 가정. 이를 3으로 나누면 한 배너당 640.00000...픽셀이 되어야 하는데, 부동소수점 오차 때문에 모니터는 이를 정확히 표현하지 못하고 소수점 아래 픽셀을 억지로 렌더링하려고 시도.
  • 결과 : 어떤 브라우저에서는 배너와 배너 사이에 눈에 거슬리는 1픽셀짜리 미세한 틈새가 생기기도 하고, 오차가 반올림되어 위로 튀면 마지막 3번째 배너가 자리가 부족해 아래줄로 떨어져버리는 디자인 붕괴가 일어나게 됨.
금융 / 게임 / AI 분야에서 소수 오차를 어떻게 처리하는가?

 

1. 금융 분야

돈을 다루는 금융권에서는 0.00001원이라도 맞지 않다면 불법이 되거나 시스템이 마비된다. 따라서 부동소수점(float, double)을 절대 사용하지 않는다.

  • 정수형(Integer) 변환 : 모든 단위를 소수가 아닌 '센트(Cent)'나 '원' 단위의 정수로 바꾸어 계산한다. (예. 10.50달러 → 1050센트로 저장하여 연산)
  • 라이브러리 사용 : 내부적으로 숫자를 문자와 소수점 위치로 쪼개어 사람이 계산하는 것처럼 10진수 연산을 수행하는 특수 데이터 타입을 사용한다. (Python : decimal.Decimal)

 

2. 게임 분야

게임은 초당 60번에서 144번 이상 화면을 그려내야 하므로 정밀도보다는 연산 속도가 훨씬 중요하다.

  • 오차 허용 범위(Epsilon) 활용 : A == B 대신 abs(A - B) < 0.00001 처럼 '이 정도 차이면 같은걸로 치자'라는 방식을 사용.
  • 고정소수점(Fixed Point) 사용 : 과거 성능이 낮은 하드웨어나 3D 정밀 좌표 동기화가 필요한 멀티플레이어 게임에서는 정수의 일부를 소수점 아래 자릿수로 강제 지정해 다루는 고정소수점 방식을 직접 구현해 쓰기도 한다.

3. AI / 딥러닝 분야

AI 분야는 수십억 개의 파라미터(가중치)를 연산해야 하므로 오히려 정밀도를 낮춰서 속도와 메모리를 이득보는 전략을 사용.

  • 양자화(Quantization) 및 FP16/BF16 도입 : 64비트나 32비트 고정밀도 소수 대신 16비트(FP16, BF16)혹은 아예 8비트 정수(INT8)로 데이터를 구겨 넣는다. 약간의 정확도 손실이 발생하더라도 AI 모델의 추론 속도가 수 배 빨라지고 메모리 사용량이 극적으로 줄어들기 때문에 현대 AI칩(GPU, NPU) 설계의 핵심 트렌드로 자리잡았다.

'Python' 카테고리의 다른 글

컬렉션 타입 (세트)  (0) 2026.05.26
컬렉션 타입 (튜플)  (0) 2026.05.26
컴퓨터가 음수(-)를 표현하는 방법  (1) 2026.05.25
리스트의 함수와 메서드  (0) 2026.05.22
컬렉션 타입 (리스트)  (0) 2026.05.22