Skip to content

Kyunghwa Yoo

Javascript Floating Point

javascript3 min read

컴퓨터가 숫자를 다루는 방법

부끄럽게도 최근까지 몰랐던 굉장히 기본적인 내용이 있다.

1console.log(0.1 + 0.2) // 0.30000000000000004 !!
2console.log(0.1 + 0.3) // 0.4
3console.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 와 같은 상황이 발생하는 것이다.

눈으로 보는 floating-point number

눈으로 직접 봐야 더 잘 와닿을 것 같아서 online converter 를 찾아봤더니 역시나 있다. (내가 필요하다고 느끼는 건 거진 누가 이미 만들어놨다.. ㅋㅋ) 일반적인 10진수 소수점을 float 으로 컨버팅해주고 그걸 다시 이해하기 쉽게 10진수로 바꿔주는 컨버터다.

여기는 시각화가 잘되어있다. 장점은 부호, 지수, 가수 를 구분해서 보여준다. 단점은 32bit float 형만 있다. 자바스크립트는 64bit를 사용하기 때문에 약간 아쉽다.

여기는 64bit를 지원한다. 들어가서 0.1을 입력하고 convert 해보자.

0.1000000000000000055511151231257827021181583404541015625 이 나온다.

그 다음에는 0.2를 입력하고 convert 해보자.

0.200000000000000011102230246251565404236316680908203125 가 나온다.

둘을 더해볼까?

여기에서 계산기를 제공한다. 첫 번째에 0.1을 넣고 두 번째에 0.2 를 넣고 + 버튼을 눌러보자.

10.1000000000000000055511151231257827021181583404541015625
2+ 0.200000000000000011102230246251565404236316680908203125
3----------------------------------------------------------------
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가지를 제시하고 있다.

  1. 중요한 연산작업(예를 들면 돈 계산)일 경우 특별한 타입을 사용한다.
    • 이 부분은 별로 공감되지 않는다. 반쪽짜리 해결책 같다.
  2. toFixed 메소드나 toPercision 메소드를 이용해서 반올림을 한다.
  3. 정수로 바꿔서 계산하기. (0.1 * 10 + 0.2 * 10) / 10 = 0.3
    • 하지만 이 것은 더 많은 작업을 초래하고 또 다른 문제를 야기시킬 수 있다.

근데 개인적으로 자바스크립트에서 소수점 연산을 할 때는 bignumber.js 가 짱인거 같다. 그럼 bignumber는 어떻게 해결한걸까? 이건 좀 더 알아봐야겠다. 예상은 혜결책 1번의 Arbitrary-Precision Decimal 처럼 가수나 지수의 길이를 늘려서 오차를 줄이는 방법일 것 같다.


DEEP DIVING! 깊게 들어가보자!

이왕 이렇게 된 거 좀 더 deep 하게 부동소수점에 대해 알아보자!

용어 정리

  • float: 32bit안에서 숫자를 표시하는 방법
    • 0 ~ 22 bit: 가수
    • 23 ~ 30 bit: 지수
    • 31 bit: 부호
  • double: 64bit안에서 숫자를 표시하는 방법
    • 0 ~ 51 bit: 가수
    • 52 ~ 62 bit: 지수
    • 63 bit: 부호
    • float 보다 2배 더 정밀하게 숫자를 표현할 수 있지만 2배 더 용량을 차지한다.
    • 자바스크립트는 double로 숫자를 표현한다.
  • 가수(Fraction / Mantissa / Significand): 실제 값을 나타내는 수
  • 지수(Exponent): 가수에서 소수점을 몇번째에 찍어야 되는지 나타내는 수
  • 부호(Sign): 숫자가 음수인지 양수인지 구분하는 값. 0이면 양수이고, 1이면 음수이다.

십진수 실수값을 floating-point number 로 변환하는 과정

예시와 함께 설명해보려고 한다. 십진수 실수값 -43.25 를 32bit floating-point number 로 변환해보자.

  1. 십진수를 이진수로 변환한다.
    • 43을 이진수로 바꾸면 101011 이 된다.
    • 0.25 를 이진수로 바꾸면 01 이 된다.
    • 합치면 -101011.01 이 된다.
  2. 정규화 작업을 거친다.
    • 이진수로 변환한 값을 1.xxxx 형식으로 소수점이 첫번째에 오도록 만드는 작업을 정규화 작업이라고 한다.
    • -101011.01 을 -1.0101101 로 만들려면 소수점을 5번 이동해야 한다.
    • 지금 다루고 있는 진법이 이진법 이기 때문에 2를 5번 곱해줘야 한다. (일반적인 10진법의 경우 10을 곱해야 소수점이 앞으로 이동하는 것처럼)
    • -101011.01 = -1.0101 * 2^5 가 될 수 있겠다.
    • 이 때 부호는 1이 된다. (음수니까)
    • 지수는 5가 나왔지만 바이어스 표현법을 적용시키는 작업이 남아있다.
    • 가수는 0101101 이 된다. 소수점 앞의 1은 무조건 1밖에 될 수 없기 때문에 bit수를 아끼기 위해 생략한다. (아무 십진수나 이진수로 바꿔보면 알 수 있다.)
  3. 지수에 바이어스 표현법을 적용시킨다.
    • 바이어스 표현법은 지수에 바이어스 상수값을 더해주는 방법으로 음수를 표현하기 위한 방법이라고만 알고 있자. (참고)
    • 바이어스 상수 계산법(밑수^지수-1 -1)에서 이진수를 다루기 때문에 밑수는 2가 되고 지수가 들어갈 공간이 8bit 이기 때문에 지수는 8 - 1 = 7 이 된다.
    • 계산을 하면 바이어스 상수는 2^7 - 1 = 127 이다.
    • 정규화 작업에서 나왔던 지수값 5에 바이어스 상수 127을 더한 값 132가 지수가 된다. (이 것이 바이어스 표현법이다.)
    • 132를 이진수로 변환한 10000100 이 실제로 bit값이 된다.

온라인 컨버팅 시각화 사이트 에 들어가서 -43.25 를 입력해보자. 계산한 대로 Sign(부호) 는 1을 표시할 것이다. Exponent(지수) 는 132를 이진수로 변환한 1000100 이 표시될 것이다. Mantissa(가수) 는 정규화 작업을 거쳐 나왔던 0101101 에 남은 자리를 0으로 채운 01011010000000000000000 이 표시된다.

IEEE 754

  • 프로그래밍 언어에서는 표준화된 IEEE 754 부동소수점 방식으로 숫자를 표현한다.
  • 자바스크립트는 64비트 부동 소수점 (double precision floating point) 포맷으로 저장한다. (다른 프로그래밍 언어와 다르게 숫자타입이 오직 하나다.)
© 2020 by Kyunghwa Yoo.