본문 바로가기
개발/자바스크립트

[자바스크립트] number 타입에 대하여

by 핸디(Handy) 2021. 3. 17.

들어가며

개발 관련 영상을 보다가 이런 질문이 나왔다.

"자바스크립트에서 숫자 타입이 하나뿐인 이유를 설명하시오"

음.. 일단 내가 하는 자바스크립트는 숫자 타입은 number, bigint로 2개인데 잘못됐나 싶었다.

그래서 질문자의 의도를 생각해보니 숫자 타입, 정확히는 number 타입이 하나뿐인 이유를 설명하시오 라고 하니 질문이 이해가 되었고 그에 대한 답을 스스로 해보고자 이번 포스트를 작성하게 되었다.


JS의 7개 표준 자료형

일단 최신 ECMAScript 표준에서는 기본형 값에 대해 7개의 타입을 정의하고 있다.

기본 자료형(Primitive) 7가지 : string, number, boolean, undefined,null, symbol, Bigint

여기서 string, number, boolean, null 은 자바스크립트 초장기부터 존재해왔으며, symbol 기본형은 ES2015에서 추가되었고 Bigint는 확정단계에 있다.

기본형들은 일단 불변(immutable)이며 매서드를 가지고 있지 않는다는 점에서 객체와 구분된다.

7개의 자료형 중에서 이번 포스트에서는 Number에 대해 진득하게 알아보도록 하자.


Number 자료형

ECMAScript 표준에 따르면, 숫자의 자료형은 64비트 형식을 이용한 단 하나만 존재한다고 서술되어 있다.

자바처럼 float, double, int 처럼 별도의 자료형이 없다. 

또한 자바스크립트의 Number 타입에는 숫자뿐만 아니라 몇 가지 추가적인 값을 가질 수 있는데, 바로 +무한대, -무한대, NaN(Not a Number)이다. 

특별한 3가지 값을 처리하기전에 더 이상한 값을 소개하고자 한다. 숫자에서 가장 위대한 발견이라는 0(zero)에 대한 내용이다.

자바스크립트에서는 유일하게 0은 2가지 방식으로 표현이 가능하다. 바로 +0과 -0이다. 0이란 양수와 음수의 기준이 되는 값이다. 따라서 부호를 붙일 수도 있고 없을 수도 있다. 하지만 일반적으로 우리가 쓰는 0은 +0인데 부호가 생략된 것이다.

무한대

+0과 -0은 사용하는데 별다른 차이가 없다.

+0 === -0 // true

하지만 무한대의 상황에서는 차이가 생기는 값이다. +,- 무한대는 코드로 나타내면 다음과 같은 상황이다.

let positiveInfinity = 10 / +0; 
console.log(positiveInfinity); // Infinity

let negativeInfinity = 10 / -0; 
console.log(positiveInfinity); // -Infinity

console.log(typeof(positiveInfinity), typeof(negativeInfinity)); // number, number

이렇게 부호가 있는 무한대가 되며, 당연히 타입은 둘다 number이다.

NaN

NaN에 대해서 알아보자. 

let nan = 10 / "string";
console.log(nan); // NaN

간단히 설명하자면 number의 의도를 가졌으나 number가 아니게 된 값을 의미한다.

positiveInfinity === positiveInfinity // true
negativeInfinity === negativeInfinity // true
positiveInfinity === negativeInfinity // false
nan === nan // false
nan === nan // false

그리고 비교를 해보면 위처럼 나온다. NaN은 같지 않음을 다시 한번 확인해라. 그러면 둘 다 NaN일 때 같을 경우는 어떻게 판단할 것인가.

아주 얄팍한 생각으로 string으로 구별하면 되지 않을까 생각했다. 하지만 자바스크립트에서 친절하게 메서드를 지원한다.(물론 ES6부터다)

let nan = 10 / "string";
String(nan) === String(nan) // true
Number.isNaN(nan); // true

여기서 더 깊숙히 들어가 보자. 

자바스크립트에서는 isNaN이 두 개가 있다. isNaN, Number.isNaN. 이 두 개의 차이점을 간단히 살펴보기 위해 타입 스크립트에서 정의한 문서를 보자.( 세부 구현까지 안 볼 수 있도록 잘 설명해놓았다 ㅎㅎ )

isNaN()

Number.isNaN()

보면 둘다 boolean를 반환하는데 받는 타입이 다르다. 글로벌 isNaN은 number이지만 Number.isNaN은 unknown으로 받는다. 

타입스크립트에 익숙하지 않더라도 둘이 다르다는 것은 명백해 보인다.

조금 더 설명을 보자면 global isNaN과 달리 Number.isNaN은 number타입으로 강제 형변환이 일어나지 않는다고 되어있다. 

예시로 보자.

isNaN(0 / 0); // true 
isNaN("string"); // true
isNaN(10 / "a") // true
isNaN(undefined); // true

isNaN(37); // false
isNaN(null); // false

 global isNaN은 혼란하다. 하지만 Number.isNaN은 

Number.isNaN("string"); // false
Number.isNaN(undefined); // false

있는 그대로 들고가서 NaN를 판별해서 혼란함이 사라졌다. 이러한 혼란이 생기는 이유는 자바스크립트의 형변환이 매우 더럽기 때문이다. 그러니 Number.isNaN()를 쓰는 것이 현명한 방법이다.


다시 Number로 돌아가서 내용을 이어가겠다.

방금전까지 특별한 값들에 대해 알아봤으니 일반적인 사용법에 대해 알아보자.

[ 지수형 ]

const a = 1E10
a; // 10000000000
1 / a; // 1e-10
a.toExponential(); // "1e+10"

 

[ 소수값]

let a = 0.1;
a; // 0.1
a.toExponential(); // "1e-1"

이렇게 보니 자바스크립트의 number 타입은 아주 간단하구나 라고 생각할 수 있다.

하지만 함정은 여기서부터 시작된다. 소수값들끼리 연산을 한번 해보자.

0.1 + 0.2 === 0.3; // false
0.1 * 0.2 === 0.02; // false

???? 우리가 이때까지 배웠던 수학 지식들이 전부 다 망해버렸다.

값을 확인해보니

0.30000000000000004 ?? 맨 마지막 4는 무엇이란 말인가

Machine Epsilon(머신 입실론)

여기에 이제 새로운 개념인 Machine Epsilon(머신 입실론)이 나온다. 실수를 연속된 숫자로 개념적으로 이해하는 우리와 달리 컴퓨터는 실수를 연속적으로 표현할 수가 없다. 

예를 들어  1/3은 0.3333333... 3으로 무한으로 이어진다고 알지만 컴퓨터는 어느 순간 마지막 3을 끊고 특정값으로 설정해야 할 필요가 있다. 그렇지 않으면 1/3를 표현하기 위해 모든 메모리 공간을 쓰고도 표현을 할 수가 없다. 따라서 특정 시점으로 끊기는 그 수와 수 사이의 간격을 Machine Epsilon(머신 입실론)이라고 한다.

자바스크립트의 Machine Epsilon(머신 입실론)은 크롬에서 확인해보면 2.220446049250313e-16 가 나온다.

따라서 Machine Epsilon를 이용해 이제 Equal 처리가 가능해진다.

function equal(n1, n2) {
    return Math.abs(n1 - n2) < Number.EPSILON;
}
equal(0.1 + 0.2, 0.3); // true

하지만 일일이 모든 숫자들에 대해 별도의 처리 함수를 만드는 건 성가신 일이니 

math.js 또는 exact-math등 잘 만들어진 수학 관련 라이브러리를 사용하라고 조언할 정도다.


마치며

"자바스크립트에서 숫자 타입이 하나뿐인 이유를 설명하시오"에 대한 답변을 하기 위해 생각보다 많은 길을 돌아왔다.

이제 나만의 답변을 해보도록 하겠다.

자바스크립트에서 숫자 타입이 하나뿐인 이유는 숫자 타입이 64비트의 부동소수점을 이용해 표시함으로 프로그래밍에 필요한 모든 수 체계를 지원하기 때문입니다. 따라서 자바처럼 float, int 같은 별도의 타입 대신 number 하나로 표현할 수 있습니다.

다만, 부동소수점 연산에는 머신입실론과 같이 한계점이 존재함으로 별도의 처리를 하거나 수학 관련 라이브러리를 사용하는 것을 권장하는 것으로 알고 있습니다.


<추가자료>
Difference between isNaN and Number.isNaN

-medium.com/@ismailsimsek/difference-between-isnan-and-number-isnan-19e71c8b7faf

 

댓글