— javascript — 3 min read
부끄럽게도 최근까지 몰랐던 굉장히 기본적인 내용이 있다.
1console.log(0.1 + 0.2) // 0.30000000000000004 !!2console.log(0.1 + 0.3) // 0.43console.log(0.1 + 0.4) // 0.5
WTF? 바로 검색에 들어갔다.
원인은 컴퓨터가 이진 부동 소수점(binary floating point) 형식을 사용해서 숫자를 표현하기 때문이다.
이진 부동 소수점을 사용하는 이유는 효율적으로 컴퓨터가 숫자를 이해할 수 있게 하기 위함인데, 효율적으로 하려다 보니 정확도에서 약간의 오차가 발생하게 된다.
코드가 컴파일될 때 0.1 같은 값을 이진 부동 소수점 포맷에 맞춰서 근사값으로 만들고 반올림하게 된다. (마치 1/3 을 0.333333... 이 아닌 0.33 으로 표현하듯)
이와 같은 반올림오차가 계산을 하기 전에 발생하는 것이다.
그 오차로 인해 0.1 + 0.2 = 0.30000000000000004
와 같은 상황이 발생하는 것이다.
눈으로 직접 봐야 더 잘 와닿을 것 같아서 online converter 를 찾아봤더니 역시나 있다. (내가 필요하다고 느끼는 건 거진 누가 이미 만들어놨다.. ㅋㅋ) 일반적인 10진수 소수점을 float 으로 컨버팅해주고 그걸 다시 이해하기 쉽게 10진수로 바꿔주는 컨버터다.
여기는 시각화가 잘되어있다. 장점은 부호, 지수, 가수 를 구분해서 보여준다. 단점은 32bit float 형만 있다. 자바스크립트는 64bit를 사용하기 때문에 약간 아쉽다.
여기는 64bit를 지원한다. 들어가서 0.1을 입력하고 convert 해보자.
0.1000000000000000055511151231257827021181583404541015625
이 나온다.
그 다음에는 0.2를 입력하고 convert 해보자.
0.200000000000000011102230246251565404236316680908203125
가 나온다.
둘을 더해볼까?
여기에서 계산기를 제공한다. 첫 번째에 0.1을 넣고 두 번째에 0.2 를 넣고 + 버튼을 눌러보자.
10.10000000000000000555111512312578270211815834045410156252+ 0.2000000000000000111022302462515654042363166809082031253----------------------------------------------------------------4 3.0000000000000004E-1 5 (E가 나와서 당황했다면 E-1 을 지우고 소수점을 한 칸 앞으로 옮기자. 그럼 0.30000000000000004 가 된다.)
드디어 console.log(0.1 + 0.2)
가 0.30000000000000004
가 되는지 눈으로 확인했다.
그럼 왜 0.1 + 0.3 = 0.4
이고 0.1 + 0.4 = 0.5
인 걸까?
0.1 + 0.3 = 0.4
는 실제 결과값은 0.4는 아니지만 굉장히 0.4와 가깝기 때문에 0.4가 된다.
반올림한 결과가 마침 0.4 인 것이다.
0.1 + 0.4 = 0.5
는 정확하게 floating-point number에 일치한다.
한마디로 딱 떨어진다. 계산기에 찍어봐도 깔끔하게 떨어지는 것을 확인할 수 있다.
0.1과 0.4의 변환값의 오차를 서로가 캔슬시켜준다고 한다.
자 이제 자바스크립트가 어떻게 숫자와 소수점을 다루고 계산하는지 알았으니 (아직 잘 모르겠다면 floating point number 에 대해 더 검색해보자...ㅠㅠ) 해결책을 찾아보자. 해결책은 이 곳 에서 3가지를 제시하고 있다.
toFixed
메소드나 toPercision
메소드를 이용해서 반올림을 한다.(0.1 * 10 + 0.2 * 10) / 10 = 0.3
근데 개인적으로 자바스크립트에서 소수점 연산을 할 때는 bignumber.js 가 짱인거 같다. 그럼 bignumber는 어떻게 해결한걸까? 이건 좀 더 알아봐야겠다. 예상은 혜결책 1번의 Arbitrary-Precision Decimal 처럼 가수나 지수의 길이를 늘려서 오차를 줄이는 방법일 것 같다.
이왕 이렇게 된 거 좀 더 deep 하게 부동소수점에 대해 알아보자!
예시와 함께 설명해보려고 한다. 십진수 실수값 -43.25 를 32bit floating-point number 로 변환해보자.
온라인 컨버팅 시각화 사이트 에 들어가서 -43.25 를 입력해보자. 계산한 대로 Sign(부호) 는 1을 표시할 것이다. Exponent(지수) 는 132를 이진수로 변환한 1000100 이 표시될 것이다. Mantissa(가수) 는 정규화 작업을 거쳐 나왔던 0101101 에 남은 자리를 0으로 채운 01011010000000000000000 이 표시된다.