부동소수점이란? (0.1 + 0.2 == 0.3 -> False)

Sea Panda·2023년 5월 2일
0

위와 같은 문제를 많이 접해봤을 것이다. 이번 글에서는 미루고 미뤘던 부동소수점에 대해서 다루어 보려고 한다.

컴퓨터의 수

대부분의 프로그래밍 언어에서는 수를 표현하기 위해 크게 두 가지 타입을 제공한다. 바로 정수 타입(int in python)과, 부동소수점 타입(float in python)이다.

정수도 실수에 포함이 되는데 왜 정수타입과 부동소수점 타입을 애써 나눠 놓은 것일까? 이유는 무척 간단하다. 부동소주점이 실수를 완벽하게 표한할 수 없기 때문이다. 정확하게 따지고 들어가면 정수조차도 제대로 표현하지 못한다고 한다. 즉, 정수를 가지고 계산을 할 때, 부동소수점 타입을 사용할 경우 정확한 결과를 얻을 수 없게 된다.

그리고 또 한 가지 이유는, 보통 부동소수점이 1보다 작은 소수를 표현하기 위해 도입된 개념이라는 인식을 많이들 가지고 있다. 아마도 이름 자체에 "소수점"이라는 표현이 들어가있기 때문일 것이다. 분명 그런 목적도 있긴 하지만 본래 목적의 일부분만을 나타낼 뿐이다. 부동소수점은 아주 작은 수와 아주 큰 수 양쪽을 모두! 표현하기 위해서 도입됐다.


고정소수점과 부동소수점

부동소수점은 부동이라는 말에서 알 수 있듯이 실수를 표현할 때 소수점의 위치를 고정하지 않는 것을 말한다. 그렇다면 왜 고정하지 않는 것일까. 만약 123.456를 표현한다고 할 때 고정소수점은 정수 부분 123456을 나눠서 표현해야 한다. 결국 한정된 비트에 정수와 소수 부분을 분할해 배치할 경우 고정소수점이 나타낼 수 있는 범위가 무척 한정된다.

그에 비하여 부동소수점에서는 123.456을 123456이라는 유효숫자와 3이라는 소수점 위치를 통해서 고정소수점보다 훨씬 넓은 범위의 수를 표현할 수 있다는 장점이 있다. 그래서 프로그래밍에서 실수를 표현할 때는 부동수서점을 주로 사용하게 된다. 이런 부동소수점은 계산기에서도 볼 수 있다. 계산기가 표현할 수 있는 한계(칸)을 넘어설 경우 e가 나오면서 지수 표기법이 나오는 것을 확인할 수 있다.(주로 공학용계산기에서 쉽게 확인할 수 있다.) 여기서 e가 바로 부동소수점, 즉 소수점의 위치를 표시하겠다는 의미다.(C++도 이런 방식을 이용하여 부동 소수점을 표현한다.)

123과 456을 저장하는게 왜 123456과 3을 저장하는 것보다 왜 넓은 범위의 수를 표현할 수 있다는건가요?


보통 한정된 비트안에서 각각의 부분을 할당하여 수를 표시하게됩니다. 그리고 위의 이미지는 고정소수점 방식에서 비트할당을 나타낸 것입니다. 정수를 표현하는 bit를 늘리면 큰 숫자는 표현할 수 있지만 소수부가 작아져서 그만큼 정밀한 숫자를 표현하기 힘들어집니다. 그 반대 역시 Trade-off관계이기 때문에 마찬가지입니다.

이와 달리 부동소수점 방식은 하나의 실수를 가수부와 지수부로 나누어 표현합니다. 이런 표현방식은 매우 큰 실수까지 표현할 수 있습니다. 따라서 현재 대부분의 시스템에서는 부동 소수점 방식으로 실수를 표현하고 있습니다.

부호 비트는 0일 경우 양수, 1일 때 음수를 나타냅니다. 만일 어떤 수를 이진법으로 나타내면 1001101.011001011 라고 가정해봅시다. 이러면 부호는 양수이기 때문에 0이 됩니다. 그리고 주어진 수를 맨 앞에 있는 1 바로 뒤로 소수점을 옮겨서 표현하도록 변환합니다. 그러면 1.001101011001011*2^6으로 표현됩니다. 이때 6을 이진법으로 나타내면 110이고 IEEE754 표현방식에서는 127을 더해서 지수를 기록하기 때문에 지수부분은 10000110이 되고, 가수부분이 001101011001011이 되는 것입니다.

이런 방식을 통하여 고정소수점보다 훨씬 넓은 범위의 수를 표현할 수 있게 됩니다. 하지만 이런 방식의 표현은 항상 오차가 존재한다는 단점을 가지고 있습니다.


2진법과 부동소수점

정수를 2진법으로 나타내는 것은 무척 간단하다. 그렇다면 소수부분은 어떻게 2진법으로 표현할까? 이는 초등학교에서 배운 방식의 반대로 생각하면 된다. 정수 부분을 2진수로 변환할 때는 2로 나눠서 나머지를 구했다면, 소수 부분의 경우 2를 곱해서 정수 부분을 취하면 된다. 만약 0.6875를 2진수로 변환한다면 절차는 다음 그림과 같다.

위의 그림에서는 0.0이 나와서 멈췄지만, 보통의 경우 대부분의 실수는 무한히 순환하면 반복된다. 즉, 적당한 개수의 유효숫자만을 취하게 되고, 여기서 앞 서 언급한 오차가 필히 발생하게 된다.

그리고 위의 그림에서도 볼 수 있듯이 32비트 방식에서 가수부는 23비트가 할당되기 때문에 만약 2232^{23}보다 큰 실수의 소수점은 남지 못하고 삭제된다. 즉, 지수부의 지수가 23 이상일 경우에는 더 이상 소수점을 표현할 수 없다는 의미이다.


다시 처음으로..

그렇다면 이제 0.1 + 0.2 == 0.3이 왜 False인가에 대해서 대답하면 "파이썬의 Float는 부동소수점 방식으로 수를 저장하기 때문이다."라고 대답할 수 있다.

실제로 0.1과 0.2(두번째 사진, 소수점 30자리까지 표현)을 파이썬에서 저장하는 값은 위와 같다고 한다.그리고 0.3(소수점 30자리까지 표현)은 다음과 같은 값은 가진다.

❗️출처
1. 파이썬 0.1+0.2 not same as 0.3 사진출처
2. C++ 프로그래밍: 부동소수점 구조와 원리
3. 부동 소수점 수
4. 부동 소수점 산술: 문제점 및 한계

0개의 댓글