본문 바로가기

Backend/Javascript

[Javascript] - Number

Number (숫자)

 

- 자바스크립트의 수는 실수(real-number) 에서 영감을 받았지만 진짜 실수(real-number) 는 아닙니다.

 

- 자바스크립트는 number 라고 하는 하나의 숫자형을 갖고 있습니다. 따라서 오는 이점은

 

  • 숫자 타입간의 고민을 하지 않아도 되서 개발자의 생산성 증가
  • 타입 변환으로 인한 오류 없음
  • int 형을 사용해서 발생하는 오버플로 문제도 일어나지 않음 (아래 자바와 비교)
    • Javascript : 2147483647 + 1    // 2147483648 정확하게 맞음
    • Java : 2147483647 + 1    // -2147483648 완전히 잘못됨

- 인텔의 iAPX-432 프로세서를 위해 처음 개발된 IEEE 부동소수점 연산 표준(IEEE 754) 을 차용했습니다.

 

  • 부동소수점 컨셉 : 두 개의 수로 하나의 숫자를 표현하는 것 (계수 + 지수)
  • IEEE 754 전체를 사용하지는 않고, 자바의 double 과 밀접 -> 64비트 2진 부동소수점 타입
  • 한 개의 부호 비트와 11비트의 지수, 53비트의 유효 숫자로 구성 -> 부호 * 유효 숫자 * (2 ** 지수)
    • 총 65비트지만 64비트로 표현 가능한 이유는, 유효 숫자의 범위가 일반적으로 0.5 <= x < 1.0 사이의 2진 소수를 나타내는데, 이 값을 2진수로 나타내면 최상위 비트는 항상 1이고 이 값을 굳이 숫자에 저장할 필요가 없어서 64비트로 표현이 가능하다.
    • 위의 65비트를 64비트로 나타낼 수 있는 이유는 스스로 정확히 이해가 안돼서 검색에 나온 부분을 차용했습니다. 추후에 따로 정리해보도록 하겠습니다.

- 위의 시스템에 의해 과거에 큰 성능상의 이득을 볼 수 있었습니다. (현재는 미미)

 

- 지수는 NaN 이나 Infinity, 아주 작은 수나 0 을 나타낼 수 있습니다.

 

 

- 영 (0)

 

IEEE 754 표준에는 0 과 -0 이라는 두 개의 0 이 있지만, 아래의 케이스를 제외하면 -0 은 신경쓰지 않아도 무방합니다.

 

(1/0) === (1/-0) // false (하지만 어떤 숫자를 0으로 나눌 일이 있을지..)
Object.is(0, -0) // false (Object.is : 두 값이 같은지 판별)

 

- 숫자 리터럴

 

자바스크립트에는 18437736874454810627 개의 불변 숫자 객체가 내장되어 있고 각각은 고유하게 숫자를 나타냅니다.

Infinity 는 표현하기에 너무 큰 모든 숫자를 나타내며, Infinity 와 ∞ 는 같지 않습니다.

NaN 은 숫자가 아닌 숫자를 나타내는 특별한 값입니다. Not a Number 를 뜻하지만 typeof 연산자는 NaN 을 number 형으로 표시하기 때문에 헷갈릴 수 있습니다.

NaN 과 NaN 을 동등 연산자로 비교해보면 서로 다르다는 결과가 나오며 IEEE 754 의 부분 중 하나입니다.

 

NaN === NaN // false
Number.isNaN(value) // value 가 NaN 인지 아닌지 테스트하는 함수
Number.isFinite(value) // value 가 NaN, Infinity, -Infinity 인 경우 false

 

- Number

 

number 가 아닌 Number 는 숫자를 만드는 함수입니다. (자바스크립트의 수 = 불변 객체)

new 를 사용한 Number 생성자로도 사용할 순 있지만 이때는 Number 객체가 생성되며, 원시 숫자 자료형을 만들기 위해선

Number 함수를 사용해야 합니다.

 

const good_number = Number("432"); // 432
const bad_number = new Number("432"); // [Number: 432]
typeof good_number // "number"
typeof bad_number // "object"
good_number === bad_number // false

 

Number 는 몇몇 상수를 포함하고 있습니다.

 

Number.EPSILON
/*
 * 2.220446049250313080847263336181640625e-16
 * 1에 더했을 때 1보다 큰 수를 만들어 낼 수 있는 가장 작은 양수
 * Number.EPSILON 보다 작은 수를 1에 더해도 그 수는 1과 같음 -> 모든 고정 크기 부동 소수점 시스템이 갖는 점
*/

Number.MAX_SAFE_INTEGER
/*
 * 9007199254740991, 약 9천조
 * 자바스크립트 숫자형은 Number.MAX_SAFE_INTEGER 까지의 모든 정수형 표현 가능
 * Number.MAX_SAFE_INTEGER 보다 큰 수에 1을 더하는 것은 0을 더하는 것과 같음
 * -Number.MAX_SAFE_INTEGER 과 Number.MAX_SAFE_INTEGER 사이의 정수 값인 경우에만 올바른 정수 연산 가능
 * 이보다 큰 수는 정수로 간주하지만 대부분 틀림
*/

Number.isSafeInteger(number) // 숫자가 안전한 범위 내에 있는 경우 true
Number.isInteger(number) // 숫자가 안전한 범위던 아니던 true

Number.MAX_VALUE
/*
 * 자바스크립트가 표현할 수 있는 가장 큰 숫자
 * Number.MAX_SAFE_INTEGER * 2 ** 971
*/

Number.MIN_VALUE
/*
 * 0보다 큰 수 중에서 가장 작은 수
 * 2 ** -1074
 * 이보다 작은 양수는 0과 구별이 불가능
*/

Number.prototype // 모든 수가 상속하는 객체이며 많은 메서드가 있지만 그리 유용하진 않음

 

- 연산자

 

전위 연산자와 중위 연산자를 간단히 보도록 하겠습니다.

 

전위 연산자
+ 숫자로 변환
- 부호 변환
typeof 타입 확인

 

중위 연산자
+ 더하기
- 빼기
* 곱하기
/ 나누기
% 나머지
** 거듭제곱

 

- 비트 단위 연산자

 

자바스크립트는 C 나 다른 언어에 있는 것과 비슷한 비트 단위 연산자들을 제공합니다. 하지만 상위 몇개의 비트가 손실될 수 있기 때문에 자바스크립트에서 비트 단위 연산자를 다른 언어에 비해선 자주 사용하지 않습니다.

단항과 다항 비트 단위 연산자들을 간단히 보도록 하겠습니다.

 

단항 비트 단위 연산자
~ not 연산자

 

다항 비트 단위 연산자
& and 연산자
| or 연산자
^ exclusive (xor) 연산자
<< 왼쪽 시프트
>>> 오른쪽 시프트
>> 부호 확장 오른쪽 시프트

 

- Math 객체

 

Math 객체는 Number 에 있어야 할 여러 유용한 함수를 포함하고 있습니다.

 

// Math.floor : 더 작은 정수 (내림 혹은 버림)
Math.floor(2.5) // -3

// Math.round : 반올림
Math.round(3.4) // 3
Math.round(3.5) // 4

// Math.ceil : 더 큰 정수 (올림)
Math.ceil(2.3) // 3

// Math.trunc : 좀 더 0에 가까운 정수
Math.trunc(-2.5) // -2

// Math.min : 인자 중 가장 작은 값 반환
Math.min(1, 2) // 1

// Math.max : 인자 중 가장 큰 값 반환
Math.max(2, 5) // 5

// Math.random : 0 <= x < 1 사이 임의의 수 반환
Math.random() // 0.34... 실행할 때마다 다름

 

- 숫자 속의 괴물

 

자바스크립트는 숫자를 구성 요소로 분해하는 도구를 제공하지 않지만, 쉽게 구현할 수 있습니다. 이를 통해 숫자에 대해 좀 더 알아보도록 하겠습니다.

 

// 숫자를 분해해 부호와 정수 계수, 지수와 같은 구성요소로 나누는 함수
function deconstruct(number) {
  // number = sign * coefficient * (2 ** exponent)
  let sign = 1;
  let coefficient = number;
  let exponent = 0;
  
  // 계수에서 부호 제거
  if (coefficient < 0) {
    coefficient = -coefficient;
    sign = -1;
  }
  
  // 계수가 0이 될때까지 2로 나누고 나눈 횟수와 -1128을 더해 지수를 구함
  if (Number.isFinite(number) && number !== 0) {
    exponent = -1128;
    let reduction = coefficient;
    
    while(reduction !== 0) {
      exponent += 1;
      reduction /= 2;
    }
    
    // 지수를 줄임, 지수가 0이 아닐 경우 수정해서 계수를 바로 잡음
    reduction = exponent;
    
    while(reduction > 0) {
      coefficient /= 2;
      reduction -= 1;
    }
    
    while(reduction < 0) {
      coefficient *= 2;
      reduction += 1;
    }
  }
  
  return { sign, coefficient, exponent, number };
}

deconstruct(Number.MAX_SAFE_INTEGER)
// sign: 1, coefficient: 9007199254740991, exponent: 0, number: 9007199254740991

deconstruct(1)
// sign: 1, coefficient: 9007199254740992, exponent: -53, number: 1
// 1 과 1 * 9007199254740992 * (2 ** -53) 은 같음

deconstruct(0.1)
// sign: 1, coefficient: 7205759403792794, exponent: -56, number: 0.1
// 0.1 은 1 * 7205759403792794 * (2 ** -56) 이 아님

 

위의 결과에서 볼 수 있듯이 자바스크립트는 0.1 또는 10진 소수 값을 사용하면 그 값을 제대로 처리할 수 없습니다. 따라서 그 값을 정확히 표현할 수 있는 alias 를 사용합니다. 0.3 과 0.1 + 0.2 를 deconstruct 해도 상이한 결과가 나올 것입니다.

따라서 최대한 안전한 정수 범위 내에서 작업하는 것이 중요합니다.

 

번외) 위의 문제점에 이어서..

 

부동소수점 시스템은 내부 2진 데이터와 사람이 사용하는 10진 데이터 간 변환을 위한 여러 함수들을 제공합니다.

2진 부동소수점 시스템을 처음 사용한 사람들은 수학자, 과학자들 이었는데 이들은 노이즈 값이 많은 실험 데이터를 주로 다루었기에

이러한 값의 부정확성이 큰 문제가 되지 않았습니다.

비즈니스 사용자들은 정확한 값이 필요했기 때문에 이러한 시스템을 거부했다고 합니다.

현재 자바스크립트 외의 다른 언어에서 2진 부동소수점 시스템의 단점이 극복되었는지는 모르겠습니다. 혹시 있다면, 추후에 다시 정리해보겠습니다.

'Backend > Javascript' 카테고리의 다른 글

[Javascript] - Object  (0) 2020.10.31
[Javascript] - Array  (0) 2020.10.24
[Javascript] - Boolean  (0) 2020.10.17
[Javascript] - Big Integer  (0) 2020.10.10
[Javascript] - Name  (0) 2020.09.26