본문 바로가기

Javascript

0.1 + 0.2 === 0.3 ... false?!

글을 시작하기에 앞서 잘못된 내용이 있다면 댓글에 남겨주세요!

댓글에 남겨주신 내용은 확인해서 수정하겠습니다!


console.log(0.1 + 0.2 === 0.3);

 

 

개발자 도구에 위와 같이 입력해 보면 이상하게 false 값이 나온다..?

 


결론부터 말하자면!

  1. 사람은 10진법, 컴퓨터는 2진법을 사용한다.

  2. 10진수에서 2진수로 변환했을 때, 무한 소수인 경우가 있다.

  3. 무한 소수인 경우에 입력받은 10진수 값에 가장 근삿값이 되도록 반올림 한 값으로 저장된다.

  4. 0.1, 0.2, 0.3은 모두 2진법으로 변환했을 때 , 무한 소수이다.

  5. 0.1과 0.2를 변환하면서 생긴 오차 때문에,
      0.1과 0.2를 각각 2진수로 변환하고 합한 값과 0.3을 2진수로 변환한 값 간에 오차가 생긴다.

  6. 때문에, 0.1 + 0.2 === 0.3은 같지 않게 된다.

 

 


이러한 이유는..

 

사람은 10진법을 사용하고, 컴퓨터는 2진법을 사용하기 때문에 일어나는 일이다.

 

사람이 10진법으로 표현(입력)하면, 컴퓨터는 2진법으로 사용(변환 / 저장)한다.

 

 


10진수를 2진수로 변환하면서 생기는 일..!

 

정수를 2진수로 변환할 때는 변환된 후에도 정수로 딱 떨어져 문제가 없다!

정수를 10진수에서 2진수로 변환하는 방식

 

 

 

하지만 소수의 경우에는 이렇게 유한 소수로 변환되는 경우가 있는 반면,

소수를 10진수에서 2진수로 변환하는 방식

 

 

이렇게 무한 소수로 변환되는 경우도 있다.

 

 


 

 

숫자를 표현하기 위해 컴퓨터는 무한하지 않은 한정적인 메모리 공간에 숫자를 저장해야 한다.

 

변환한 2진수가 무한 소수일 경우, 컴퓨터는 입력받은 10진수 값과 가장 가까운 값으로 수를 저장하게 되는데

특정 소수 자릿수에서 반올림한 값을 저장하는 방식을 사용한다.

 

여러 가지 방법들이 있지만 이 글에서는 Javascript 환경을 중점으로 다룰 예정이다.

 

무한한 소수를 저장하기 위해 Javascript 는 다음과 같은 방식으로 실수를 저장한다.

 

 


부동소수점(浮動小數點, floating point)

 

부동소수점(浮動小數點, floating point)은 실수를 컴퓨터 상에서 근사하여 표현할 때 소수점의 위치를 고정하지 않고
그 위치를 나타내는 수를 따로 적는 것으로, 유효숫자를 나타내는 가수(假數)와 소수점의 위치를 풀이하는 지수(指數)로 나누어 표현한다.
- 위키백과

 

처음 부동이란 단어를 들었을 때, 아닐 부 / 움직일 동 을 써서 움직이지 않는다는 의미인 것처럼 보였다.

 

하지만, 뜰 부 / 움직일 동 을 써서 소수점이 떠다닌다 는 의미를 담고 있다.

 

그래서 부동소수점을 떠돌이 소수점이라고도 한다.

 

 

 


부동소수점의 구성

부동소수점에서도 반정도 (반정밀도), 단정도 (단정밀도), 배정도 (배정밀도) 등 여러 가지 종류가 있지만
우리는 Javascript 에서 사용되는 배정도에 대해서 알아볼 것이다.
각 종류는 총 bit의 크기에 따라 분류되며, 배정도는 총 64bit이다.

 

부동소수점의 구성

 

부호 (Sign)          |   8bit : 부호를 나타내며 양수는 0, 음수는 1로 표현한다.
지수 (Exponent) |  11bit  : 2진수 숫자의 지수를 나타낸다.
가수 (Mantissa)  |  52bit : 2진수 숫자의 가수를 나타낸다.

 

지금은 각 구분된 부분의 의미가 뭐가 뭔지 잘 이해 안 될 수도 있다.

 

이어지는 부동소수점을 저장하는 순서와 함께 보면 한결 쉽게 다가올 것이다.

 


부동소수점으로 저장하는 순서

1. 10진수 → 2진수로 변환

 

17.03125 라는 숫자가 있다.

정수 부분과 소수 부분을 각각 나눠서 17은 10001로, 0.03125는 0.0101로 변환되어

17.03125(10) = 10001.0101(2) 로 변환된다.

 

2. 부호

17.03125양수이기 때문에 부호 자리에 0이 들어온다.

정규화

 

 

이렇게 정수 부분에 1만 남도록 숫자를 변환하는 것을 정규화 라고 한다.

 

 

3. 지수

정규화 후의 지수 부분

지수는 4를 그대로 넣는 것이 아니고 4 + Bias 값을 2진수로 변환해서 넣어준다.

 

 

 

엥? Bias가 뭐지?

 

Bias (바이어스)는 지수 편향이라고도 하며,
지수가 음수인 경우에 지수부에 저장할 수 없기 때문에 더해주는 값이다.

 

 

Bias를 구하는 공식은
$$ 2^{(지수부분의\ 총\ bit\ -\ 1)} - 1 $$
이다.

 

 

배정밀도에서의 지수 부분은 11bit 이기 때문에

$$ Bias = 2^{(11\ -\ 1)} - 1 = 1023 $$ 

이 되겠다.

 

 

따라서, 4 + 1023 = 1027 로 지수부는 100000000011 이 된다.

 

 

. 가수

정규화 후의 가수 부분

가수 부분은 간단하다.

 

정규화 후 가수에서 정수 부분을 제외하고 그대로 넣어주면 된다.

정수 부분은 항상 1로 고정이기 때문에 저장하지 않고,

이 부분을 hidden bit 라고 한다.

 

가수로 채우고 난 이후 52bit 보다 부족한 부분은 모두 0으로 채워지게 된다.

 


그래서 왜 0.1 + 0.2 === 0.3 은 false 가 나오는 거지?

0.1을 부동소수점 방식으로 변환하면,

0.1을 부동소수점으로 표현

무한 소수이기 때문에 위와 같이 변환된다.

 

이때, 가수 부분을 넘어가는 자리에서 반올림을 하고 메모리에 저장하게 되어

이러한 형태로 저장된다.

 

그래서 0.1은

위와 같이 메모리에 저장된다.

 


0.1과 0.2는 지수의 차이만 있지 동일한 패턴의 무한 소수이기 때문에

 

0.1 = 0.0001100110011001100110011001100110011001100110011001101
0.2 = 0.001100110011001100110011001100110011001100110011001101
---------------------------------------------------------------
+     0.0100110011001100110011001100110011001100110011001100111

 

이렇게 표현되고, 아래와 같이 0.1 + 0.2 와 0.3을 비교하게 되면

 

0.1 + 0.2 = 0.0100110011001100110011001100110011001100110011001100111
0.3       = 0.010011001100110011001100110011001100110011001100110011
---------------------------------------------------------------------
            0.0000000000000000000000000000000000000000000000000000001

 

 

0.0000000000000000000000000000000000000000000000000000001의 차이가 생겨
0.1 + 0.2 === 0.3 은 false가 나온다!

 

 


결과적으로

  1. 사람은 10진법, 컴퓨터는 2진법을 사용한다.

  2. 10진수에서 2진수로 변환했을 때, 무한 소수인 경우가 있다.

  3. 무한 소수인 경우에 입력받은 10진수 값에 가장 근삿값이 되도록 반올림 한 값으로 저장된다.

  4. 0.1, 0.2, 0.3은 모두 2진법으로 변환했을 때 , 무한 소수이다.

  5. 0.1과 0.2를 변환하면서 생긴 오차 때문에,
      0.1과 0.2를 각각 2진수로 변환하고 합한 값과 0.3을 2진수로 변환한 값 간에 오차가 생긴다.

  6. 때문에, 0.1 + 0.2 === 0.3은 같지 않게 된다.