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 |