본문 바로가기

개발/Javascript

[Javascript] 0.1 + 0.2가 0.3이 아닌 이유 쉽게 이해하기

반응형

지난 글 복습하기

지난 포스팅에서 javascript에서 `-0`이 존재하는 이유에 대해 알아보았어요.

[양수 0, 음수 0 차이 쉽게 이해하기]

 

위의 글에서 이야기했던 `-0`이 존재하는 이유와 오늘 글의 주제와 관련이 있기 때문에

다시 한번 리캡해볼까요?

Recap!
- javascript는 숫자를 저장할 때 IEEE754표준에 따라서 64개의 비트(bit, 0 또는 1)를 사용한 부동소수점으로 저장한다.
- 이때 가장 앞에 오는 비트가 부호를 의미한다. 양수면 `0`, 음수면 `1`을 저장한다.
- `+0`을 저장할 때는 `0`을 저장하고 `-0`을 저장할 땐 `1`을 저장하기 때문에 차이가 발생합니다.

 

이 내용을 잘 이해해야 오늘의 이야기도 쉽게 이해할 수 있으니,

아직 잘 모르겠다 싶다면 지난 포스팅을 먼저 보고 오시는 것을 추천해요!


0.1 + 0.2 === 0.3 이 false라고요?

 

수학적으로 보았을 때 `0.1 + 0.2` 는 당연히 `0.3`입니다.

하지만 javascript 콘솔에 `0.1 + 0.2 === 0.3`을 입력하면 `false`라는 결과값을 출력합니다. 😨

 

`0.1 + 0.2`가 어떤 값이길래 `0.3`과 다르다고 하는 것일까요?


콘솔에 `0.1 + 0.2`를 찍어보니 `0.30000000000000004`라는 값을 출력합니다.

그렇다면 `0.1 + 0.2`이 `0.3`보다 큰 값이라는 것이니,

`0.1 + 0.2 > 0.3` 을 찍어보니 `true`를 출력하네요. 

 

0.00000000000000004는 어디에서 온 값일까요? 🧐

 


원인은  부동소수점에 있다!

위키피디아 Double-precision_floating-point_format

별로 친숙하지 않은 이미지이지만, 다시 한번 들여다봅시다.

javascript는 숫자를 64bit를 사용한 부동소수점으로 형식으로 변환하여 저장합니다.

 

그렇다면 오늘 글의 주인공 `0.1`과 `0.2`이 각각 어떻게 변환되는지 알아볼까요?

물론 우리의 목적은 "쉽게 이해하기"이기 때문에 직접 계산하지 않을 거예요. (저도 계산하는 방법 잘 기억 안 나요 ㅋㅋㅋ)

 

찾아보니 십진법을 [부동소수점으로 변환하는 사이트]가 있더라구요!

감사히 사용하겠습니다. 🥹👍🏻


부동소수점 변환 결과

`0.1`을 부동소수점으로 변환한 결과입니다.

`0` `01111111011` `1001100110011001100110011001100110011001100110011010`

 

여기부터 집중!

 

`10`을 `3`으로 나누었을 때 `3.333333`이 반복되는 것처럼

`0.1`도 부동소수점으로 변환하는 과정에서 딱 떨어지지 않고, `11001100`이 무한으로 반복되어요. 

무한반복되는 값을 64bit에 저장해야 하니 결국 ★반올림하게 되고,이때 오차가 발생합니다!

🔍 추가로 알아보기 : 왜 마지막 다섯 bit가 `11010`이 되었을까?

IEEE754 형식은 저장가능한 비트를 초과했을 때 반올림을 해요.
십진수에서 5 이상의 값은 올림을 하는 것처럼, 이진수에서는 1을 올림 합니다.

`0.1`의 부동소수점을 확인해 보겠습니다.
원래대로라면 `11001100`이 계속 반복되어야 하는데,
마지막 다섯 bit가 `11001`이고 그다음에 오는 수는 `1`입니다.

따라서 `1`은 올림처리하기 때문에 `11001`에 `1`을 더한 `11010`이 되었어요!
즉, `0.1`보다 약간 큰 숫자로 저장이 된 것이죠!

`0.2`를 부동소수점으로 변환한 결과입니다.

`0` `01111111100` `1001100110011001100110011001100110011001100110011010`

 

`0.2`도 마찬가지로 딱 떨어지지 않고 무한 반복되어 나머지 값을 반올림하여 저장합니다.


오차가 발생한 이유

무한 반복되는 값을 64bit에 저장하다 보니 어쩔 수 없이 오차가 발생하게 되었어요.

 

`0.1`과 `0.2`는 저장하는 과정에서 올림이 발생했기 때문에,

`0.1 + 0.2`의 결과값은 `0.3`보다 약간 큰 `0.30000000000000004`라는 결과값이 나온 것이죠.

 

즉, `0.00000000000000004`는 반올림에 의해 발생한 오차값인 것입니다!


 

개발할 땐 어떻게 해야 할까?

const num1 = 0.1
const num2 = 0.2

console.log( Math.round((num1 + num2) * 10) / 10)
// 0.3

소수를 사용하고 싶은 자릿수까지 정수로 만들어서 계산한 후, 

다시 소수로 변환하면 오차가 발생하는 현상을 방지할 수 있어요!


마치며

잊지 말아요

부동소수점은 언제 배웠는지 기억도 잘 안 나는데, 이렇게 다시 만나게 될 줄이야!

 

개발을 할 때에는 0.1이 정확하게 어떤 값으로 저장되고,

0.1 + 0.2의 결과값이 정확하게 어떤 값인지 알 필요는 없어요. 

 

하지만 컴퓨터는 숫자를 부동소수점으로 변환하여 저장하고

이 과정에서 오차가 발생할 수 있다는 것은 분명히 인지를 하고 있어야 합니다.

 

그렇지 않으면 예상하지 못한 오류가 발생할 수 있어요..😨


더 나아가기

`0.1 + 0.2 === 0.3`이 `false`인 것 처럼

분명히 `true`를 반환할 것 같은데, `false`를 반환하는 비교수식이 하나 더 있어요.


바로 `NaN === NaN` 입니다.

양쪽 항이 똑같은 모양을 하고 있는데 다르다니요 (???)

 

이 또한 IEEE754표준과 관련이 있습니다.

다음 글에서 알아보도록 합시다!

 


추천글

 

[Javascript] 양수0, 음수0 차이 쉽게 이해하기

+0과 -0 알아보기수학적으로 0은 음수와 양수의 사이에 차이가 없기 때문에 같은 값으로 봅니다.하지만 컴퓨터 세계에서는 `+0`과 `-0`은 엄밀히 말하면 다릅니다.  javascript 콘솔에서 `-0`을 입력하

shelly-log.tistory.com

 

반응형