Hyunjung Im

Frontend Developer

github

JavaScript에서 0.1 + 0.2는 0.3일까?

2022-05-07

JavaScript에서 0.1 + 0.2는 0.3일까?

0.1 + 0.2 === 0.3; // false

typeof 0.1; // 'number'

const a = 0.1;
const b = 0.2;

a + b === 0.3; // false

console.log(a + b); // 0.30000000000000004

결론부터 말하자면 0.3이 아닙니다.
컴퓨터는 명령을 처리하기 위해 10진법으로 처리된 것을 자신의 언어인 2진법으로 변환하는 과정을 거치게 됩니다.
위와 같은 결과는 JavaScript가 실수를 처리하는 방식으로 부동소수점 방식 을 채택하기 때문에 발생하게 됩니다.

간단하게 집고 넘어가자면…(매번 까먹으므로..)

  • 정수 : 10, 400 혹은 -5 같은 모든 숫자

  • 실수 : -1, 0, 1/2, e, π 주로 실직선 위의 점 또는 십진법 전개로 표현되는 수 체계

  • 부동소수점 실수 (Floating point numbers(floats)) : 12.5, 6.7876 같이 소수점과 소수 자릿수가 있는 것

  • 배정밀도 부동소수점 실수(Double) : IEEE 754 표준 부동소수점보다 더 정확한 정밀도를 가지고 있는 특별한 데이터 타입 (Doubles는 Floats 보다 더 많은 소수 자릿수를 표현할 수 있어서 소수점 표현에는 Doubles가 더 정확함)

  • 2진수 : 10진수를 0과 1을 이용해 나타내는 데이터 타입

  • 8진수 : 10진수를 0부터 7까지의 수를 이용해 나타내는 데이터 타입

  • 16진수 : 10진수를 0부터 15까지의 수를 이용해 나타내는 데이터 타입 (1~10, A~F CSS 색상 지정할 때..)

계속 나오고 있는 부동소수점이라는 게 대체 무엇인지는 밑에서 더 자세히 알아보겠습니다. 지금은 간단하게 컴퓨터가 실수를 처리하는 방법의 하나라고만 알아두면 되겠습니다.

$ 1\over 3$ + 131331131\over 331​ + 131331131\over 331​ 은 무엇일까요?

일반적으로 1이 될 것이라고 생각하겠지만 이진법을 사용하는 컴퓨터는 그렇지가 않습니다.

$ 1\over 3$ = 0.3333333333… 무한대까지 0.33333이 반복됩니다.

0.3을 2진수로 바꾸면 : 0.010011001100110011001100110011001100110011001100110011...
0.2를 2진수로 바꾸면 : 0.001100110011001100110011001100110011001100110011001101...

이렇듯이 2진수로 표현하지 못하는 소수가 발생하게 됩니다.
컴퓨터에는 제한된 양의 메모리가 있으므로 무한한 양의 숫자를 유지할 수 없으므로 근사치의 값이 저장되게 됩니다. 이 소수점을 처리하는 방식 중 하나가 부동소수점 방식 입니다.
근삿값을 저장하는 방법에는 고정소수점 방식과 부동소수점 방식이 있습니다.

IEEE-754 : JavaScript가 숫자를 처리하는 방식

ieee

벌써부터 머리가 아프기 시작하지만 알아보도록 하겠습니다.
먼저 자바스크립트가 어떻게 숫자를 처리하는 방식에 대해서 알아보겠습니다.

JavaScript 숫자는 항상 64비트 부동 소수점이다.

JavaScript는 다른 많은 프로그래밍 언어와 달리 정수, short, long, 부동소수점 같은 다양한 유형의 숫자를 정의하지 않습니다. JavaScript의 숫자는 Java, C#의 double 타입처럼 IEEE-754 표준에 따라 항상 international IEEE 754 standard로 지정됩니다.



  • Number는 소수점 이하 17자리 정도만 유지하며 산술은 반올림의 대상이 됩니다.
  • Number가 가질 수 있는 가장 큰 값은 1.8E308 그보다 더 큰 값은 특별한 Number 상수인 Infinity로 대체됩니다.
  • 자바스크립트는 실수와 정수 모두 Number라는 하나의 데이터 타입만 사용합니다. (실수와 정수 모두 같은 데이터 타입이기 때문에 하나의 방법으로 동작하게 할 수 있다는 뜻)
0.1 + 0.2 === 0.3; // false

typeof 0.1; // 'number'

const a = 0.1;
const b = 0.2;

a + b === 0.3; // false

console.log(a + b); // 0.30000000000000004

다시 본론으로 들어와서 0.1 + 0.2를 다시 입력해 보면 0.30000000000000004 가 나오게 됩니다.
저 위에 Number는 소수점 이하 17자리 정도만 유지한다고 되어있는데 저 30000000000000004의 개수가 17개인 걸 알 수 있겠죠. (세어봤는데 17개가 맞네요🙄)

파이썬을 예로 들면 파이썬의 자료형에는 정수(int)와 실수(float)가 있습니다. 파이썬의 실수 타입은 float입니다.
자바에서도 정수와 실수가 있고 실수 타입에는 float, double가 있습니다. float는 32비트를 가지고 실수를 표현하고 double은 64비트입니다.


하지만 자바스크립트는 숫자를 정의하지 않습니다.

즉 JS에서 37과 같은 숫자 리터럴은 정수가 아니라 부동 소수점 값입니다. (이제는 BigInt 타입이 있지만 Number를 대체하도록 설계되지 않았다고 하네요.)

https://0.30000000000000004.com/ 이러한 사이트도 있네용.

여기서 배울 수 있는 교훈은 소수점을 처리할 때 대부분의 경우 이 차이가 너무 작아 무시할 수 있지만 실수 연산이 무조건 정확한 처리를 가져올 순 없다는 것을 염두에 두어야 한다는 것이겠죠. 정확한 소수점 연산이 필요할 때는 다른 방식을 고려해야 하겠죠. 예를 들면 금용 쪽의 일을 처리하나던가… 그런.. 일이 있다면.. 어떻게 해야 할지는 그때 가서 다시 공부를…

밑에서는 실수를 처리하는 방법인 고정 소수점, 부동소수점에 대해서 조금만.. 알아보겠습니다.

고정 소수점 fixed point

소수점을 사용하여 고정된 자릿수의 소수를 나타내는 것. 실수는 보통 정수부와 소수부로 나눌 수 있습니다. 실수를 표현하는 가장 간단한 방식은 소수부의 자릿수를 미리 정하여, 고정된 자릿수의 소수를 표현하는 것 이 방식은 정수부와 소수부의 자릿수가 크지 않으므로, 표현할 수 있는 범위가 매우 적다는 단점이 있습니다. (한정된 메모리에서 부동소수점 방식보다 좁은 범위의 수만 나타낼 수 있습니다.)

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

움직일

하나의 실수를 가수부와 지수부로 나누어 표현하는 방식. 실수를 컴퓨터상에서 근사하여 표현할 때 소수점의 위치를 고정하지 않고 그 위치를 나타내는 수를 따로 적는 것. (떠돌이 소수점 방식이라고도 합니다.)

IEEE의 부동소수점 방식

IEEE 754는 전기 전자 기술자 협회 (IEEE)에서 개발한 컴퓨터에서 부동소수점을 표현하는 가장 널리 쓰이는 표준이다. IEEE 754의 부동 소수점 표현은 크게 세 부분으로 구성되는데, 최상위 비트는 부호를 표시하는 데 사용되며, 지수 부분(exponent)과 가수 부분(fraction/mantissa)이 있습니다.

ieee ( 맨 앞자리는 부호를 표시하는 데 사용되는데 1이면 음수 0이면 양수입니다.)
  • 정확도 문제

    • 부동 소수점으로 표현한 수가 실수를 정확히 표현하지 못하고 부동 소수점 연산 역시 실제 숳가적 연산을 정확히 표현하지 못하는 것은 여러 가지 문제를 야기합니다.
  • 규격

    • 실제 사용되고 있는 부동 소수점 방식은 대부분 IEEE 754 표준을 따릅니다.

부동소수점 방식의 오차

부동 소수점 방식으로 표현하는 실수는 항상 오차가 존재하는 단점을 가지고 있습니다. 컴퓨터에서 실수를 표현할 때는 정확한 표현이 아닌 근사치를 표현하게 됩니다.

참조 및 출처