[JS] 숫자형

MJ·2022년 9월 15일
0

Java Script

목록 보기
39/57
post-thumbnail

숫자형

모던 JS는 숫자를 나타내는 두 가지 자료형을 지원합니다.

  • 일반적인 숫자는 '배정밀도 부동소수점 숫자'로 알려진 64비트 형식의 IEEE-745에 저장
    됩니다. 포스팅에서는 이 방식을 사용할 것 입니다.

  • 임의의 길이를 가진 정수는 BigInt 숫자로 나타낼 수 있습니다. 일반적인 숫자는
    2의53승 이상이거나 -2의53승 이하일 수 없다는 제약 때문에 만들어진 자료형이
    BigInt 입니다.


숫자를 입력하는 다양한 방법

10억이라는 숫자를 변수에 할당한다고 생각해봅시다. 가장 분명한 방법은 10억의 값 자체를
할당해주면 되겟죠.

let billion = 1000000000;	

이러한 방식은 값 입력 도중에 0이 누락되는 경우가 생길 수 있습니다. 이렇게 범위가 큰
숫자들은 BigInt 자료형을 사용해서 간단하게 할당할 수 있습니다.
숫자 옆에 e를 붙이고 0의 개수를 적어주면, 개수만큼 0이 숫자에 할당됩니다.

let billion = 1e9;	// 10억 | e9는 0을 9개 붙인다.
alert( 73e8 );		// 73억

e는 10의 거듭제곱을 수행한다.

e를 숫자뒤에 붙이면, 좌측의 수를 e의 개수만큼 10의 거듭제곱을 수행합니다.

1e3 = 1 * ( 10 * 10 * 10 ) = 1,000
1.23e4 = 1.23 * ( 10 * 10 * 10 * 10 ) = 12,300


/* */
1e3, 1 * 1000과 같다.
1.23e41.23 * 10000과 같다.

아주 작은 숫자도 e를 붙여서 표현할 수 있다. 예를 들면 1마이크로초(백만분의 1초)

let ms = 0.000001 
let ms2 = 1e-6	// 1을 소수점 6번째 자리 까지 이동 시킨다. (0.000001)

이렇게 e 우측에 음수가 있다면, 이 음수의 절댓값 만큼 10을 거듭제곱한 수로 나누는 것을
의미 합니다.

1e-3 = 1	// 1 / 1000 = 0.001 | 10을 세 번 거듭제곱한 숫자로 나눈다.
1.23e-4 = 	// 1.23 / 10000 = 0.000123 | 10을 네 번 거듭제곱한 숫자로 나눈다.



16진수

16진수는 색을 나타내거나 문자를 인코딩할 떄 사용됩니다. 다양한 곳에서 사용되는 만큼
16진수도 짧게 표현할 수 있습니다. 16진수는 0x를 사용해서 표현합니다.

alert( 0xff );	// 255
alert( 0xFF ); // 255, 대 소문자를 가리지 않는다.


/* */
16진수에서 0xff2진수로 `1111 1111`입니다.
1+2+4+8+16+32+64+128 = 255의 값을 가진다.

0x라면 2진수로 `1111` 입니다.
1+2+4+8 = 15의 값을 가지겟죠.

2진수와 8진수

2진수는 0b를 사용하고, 8진수는 0o를 사용해서 값을 간단히 나타낼 수 있습니다.

let a = 0b11111111	// 255, 2진수
let b = 0o377		// 255, 8진수

alert(a == b)	// 같은 수기에 같다. true 


/* */
8진수는 0부터7까지 8개의 수로만 표현한다. 0o377은 십진수로 바꾸면 255입니다.
377 = 8*(24+7) = 결과값 + 7 하면 나옵니다.
275 = 8*(16+7) = 결과값 + 5 
170 = 8*(8+7) = 120
100 = 8*8 = 64

JS에서 지원하는 진법은 3가지 입니다. 이외의 진법은 parseInt 함수를 사용



toString(Base)

toString(base) 메서드는 base 진법으로 자신의 앞에 데이터를 평가한 후 이를 문자형으로
변환해서 반환합니다.

let num = 255;

alert( num.toString(16) );	// ff | 255값을 16진수로 변환 합니다.
alert( num.toString(2) );	// 11111111 | 255 값을 2진수로 변환 합니다.

🔔 base 별 유스케이스

base=16
16진수로 변환하며 색, 문자 인코딩을 표현할 때 사용합니다. 숫자는 0~9, 9이상의 수는
A~F로 표현 합니다.

base=2
2진수로 변환하며 비트 연산 디버깅에 주로 사용합니다. 숫자는 0 또는 1이 됩니다.

base=36
사용할 수 있는 base중 최댓값으로 숫자 0~9 문자 A~Z를 사용해서 숫자를 표현한다.
알파벳 전체가 숫자를 표기하는데 사용 됩니다. 36baseurl을 줄이는 것과 같이
숫자로 된 긴 식별자를 짧게 줄일 떄 사용 합니다.


base는 2부터 36까지 사용할 수 있습니다. 기본 값은 10


toString(36)

값을 36진수로 표현하고 문자형으로 출력해버립니다. 긴 숫자형 값을 짧게 단축시킬 수
있어서 긴 숫자를 다룰 때 효과적입니다.

alert( 123456..toString(36) );	//123456을 36진수로 변환하면 2n9c가 됩니다.

🔔 toString() 메서드 앞에 점 .. 두개

숫자를 대상으로 toString(base) 메서드를 호출하고 싶다면, 점을 2개 사용해야 합니다.
1개만 사용하면, 숫자뒤에 오는 메서드를 소수부로 인식해서 오류가 발생할 수 있습니다.
123456.ToString(36) : 소수로 인식 오류 발생
또는, (123456).toString(base) 도 가능합니다.



어림수 구하기

어림수를 구하는 것(rounding)은 숫자를 다룰 때 가장 많이 사용되는 연산 중 하나입니다.
어림 수 관련 메서드는 다음과 같습니다.


Math.floor
소수점 첫째 자리에서 내림

  • 정수 : 소수부를 버린다. (3.1 = 3.0 / 3.6 = 3.0)
  • 소수부 : 값을 1 내린다. (-1.1 = -2 / -1.6 = -2)

Math.ceil
소수점 첫째 자리에서 올림
3.1은 4가 됩니다. -1.1은 -1이 됩니다.


Math.round
소수점 첫째 자리에서 반올림
3.1은 3이 됩니다. 3.6은 4가됩니다. -1.1은 -1이 됩니다. -1.6은 -2가 됩니다.


Math.trunc (IE에서는 지원하지 않습니다)
소수를 무시
3.1은 3이 됩니다. -1.1은 -1이 됩니다.

위에서 설명한 내장 함수들만으로도 소수에 관련된 연산을 대부분 수행할 수 있습니다.
그런데, 소수점 n-th 번째 수를 기준으로 어림수를 구해야 하는 상황은 어떻게 구할까요?

예를 들어 1.23456 소수가 있는데, 소수점 두 번째 자릿수까지만 남겨서 1.23을 만들려고
합니다. 2가지 방법으로 연산할 수 있습니다.


곱셈과 나눗셈으로 소수점 자리 남기기

만약 1.23456 소수부에서 1.23으로 점 위치를 옮기고 싶다면, 어떻게 연산하는지
살펴보겠습니다.

let num = 1.23456; 

alert( Math.trunc(num * 100) / 100 );

/* */
1) 1.23456 소수에서 소수점 2 번째 자릿 수 까지만 남기고 싶다면 (1.23) 소수점의 위치를 옮겨준다.
2) 1.23456 * 100 = 123.456 > 이후에 내장 함수를 호출해서 소수를 없애준다
3) Math.trunc(123.456) = 123 > 이 값과 처음에 곱한 (100)를 나눠 준다. 
4) 123 / 100 = 1.23 | 원하는 수가 나왔다.

toFixed() 메서드로 소수점 자리 남기기

toFixed() 메서드를 사용 하면, 따로 곱셈/나눗셈 연산을 하지 않아도 자동으로
소수점 n 번쨰 수까지의 어림수를 구한 다음에, 이를 문자형으로 반환 해줍니다.

let num = 1.23456;	
alert( num.toFixed(2) );	// 1.23 | 소수점을 2번 째 자릿수 까지 반올림한다.


/* */
alert( num.toFixed(2) );
1) 1.23456 숫자에서 소수 점 3 번째를 반올림 합니다. > 1.234 에서 4를 반올림 
> 1.23이 출력 된다.

toFixed() 메서드를 사용할 때 주의할 점은, 메서드의 반환 값이 문자열이라는 것 입니다.
소수의 길이가 인수보다 작다면, 작은 만큼 끝에 0이 추가 됩니다.

let num = 12.34;
alert( num.toFixed(4) );	// 12.3400 | 소수의 길이를 4로 만들기 위해서 0을 추가

🔔 toFixed() 반환 값을 문자형 대신, 숫자형으로 반환할 수 없나요?

num.toFixed() 앞에 단항 덧셈 연산자를 사용해서 숫자로 형 변환 하거나,
Number() 함수를 호출하면 됩니다.

숫자형으로 반환 되기에, 소수가 인수보다 값이 작아도 문자열 0이 출력되지 않습니다.
숫자형에서 소수 뒷부분이 0이라면 생략할 수 있기에 0이 출력되지 않는다.



부정확한 계산

숫자는 내부적으로 64비트 형식 IEEE-754로 표현되기에 숫자를 저장하려면 정확히 64비트가
필요합니다. 64비트 중에서 1비트는 부호를 저장, 11비트는 소수점 위치를 저장, 나머지 52
비트는 숫자를 저장하는데 사용 됩니다.

숫자가 너무 커지게 되면, 64비트 공간이 넘쳐서 Infinity로 처리 됩니다.

alert(1e500);	// Infinity

원인을 이해하려면 다소 어렵기 때문에 다른 예시로 살펴보겠습니다. 연산중에 자주 발생하는
정밀도 손실(loss of precision)이라는 현상이 있습니다.

alert( 0.1 + 0.2 == 0.3 );	// false
alert( 0.1 + 0.2 ); // 0.30000000000000004


/* */
합의 결과가 0.3이 아닌 이유는?

1) 숫자는 01로 이루어진 이진수로 변환되어 연속된 메모리 공간에 저장된다.
2) 0.10.210진법을 사용하면 쉽게 표현할 수 있지만, 이진수로 변환되어 표현하면 무한소수가 된다.
3) 0.1110으로 나눈 수 입니다. (1/10) 10진법을 사용하면 쉽게 표현할 수 있죠.
4) 하지만, 13으로 나누면 0.3333 무한 소수가 됩니다. 
5) 10의 거듭제곱으로 나눈 값은 10진법에서 잘 작동하지만, 3으로 나누게 되면 10진법에서
   제대로 동작하지 않습니다.
   
6) 같은 이유로 2진법 체계에서 2의 거듭제곱으로 나눈 값은 잘 작동하지만, (1/10) 같이 2의
   거듭제곱이 아닌 값으로 나누게 되면 무한 소수가 되어 버립니다.
   
7) 10진법에서 1/3을 정확하게 표현할 수 없듯이, 2진법에서도 0.1 또는 0.2를 정확하게 저장
   하는 방법은 없습니다. 0.10.22진법으로 표현해보면 무한소수가 되는 것을 이해할
   수 있습니다. ( 0.1.toString(2) )
   
8) IEEE-754에선 가능한 가장 가까운 숫자로 반올림하는 방법을 사용해 이런 문제를 해결합니다.
   이런 반올림 규칙은 일반적으로 "작은 정밀도 손실"을 볼 수 없게 하므로, 숫자가 0.3으로
   표시됩니다. 눈에 보이지 않을 뿐 실제로 손실은 발생합니다.
   
   
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
이처럼 0.1 수에 대해서 반올림을 진행해도 손실이 발생하게 됩니다.

(0.1 + 0.2 == 0.3) false, 자세한 이해가 필요할 시 클릭 (영상 시청)


부정확한 계산 오류 해결

소수점은 2진수로 무한소수가 되는 경우가 많습니다. 소수점간의 연산을 오류 없이 수행하려면
toFixed() 메서드를 사용해서 어림 수를 구해주면 됩니다.

let sum = 0.1+0.2;
alert( sum.toFixed(2) );	// 0.30


/* */
tofixed 메서드는 항상 문자열을 반환합니다. 
숫자형으로 변환하고 싶다면 +를 붙여주면 됩니다. ( +sum.toFixed(2) )

또 다른 방법으로는 수학연산을 사용해서 소수점을 계산할 수 있습니다. 소수부를 정수로 변환
해서 덧셈 후, 결과 값을 나눗셈으로 다시 소수부를 만들어주면 됩니다.

alert((0.1 * 10 + 0.2 * 10) / 10);	// 0.3, 0.1과 0.2를 정수로 만들서서 연산 후 나눗셈진행
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001


/* */
다만 이렇게 10의 거듭제곱으로 곱하고, 다시 10으로 나누는 연산은 오류발생을 줄여주지만
완벽하게 막을 순 없습니다. 0.42 소수부는 2진법으로 무한소수가 나오기 때문입니다.

이런식으로 나오는 무한소수를 차단하라면, 어림 수를 구해서 적당한 수에서 올림/내림 하면 됩니다.

🔔 정밀도의 손실 또 다른 예시

아래 예시를 보면, 출력 값이 증가해서 나타납니다. 정밀도의 손실로인해 발생한 문제
입니다. 숫자를 저장할 때는 64비트가 사용되는데, 이 중에서 실제 숫자를 저장하는데
사용되는 52비트에 999~숫자를 저장하기엔 공간이 부족합니다. 따라서 최소 유효
숫자가 손실되어 버렸습니다.
JS는 예시처럼 숫자 손실이 일어나도 오류를 발생시키지 않습니다.

🔔 양수 0 음수 0

JS는 내부에서 숫자를 표현하는 방식으로 인해 두 종류의 0이 존재하게 됩니다.
숫자의 부호가 단일 비트에 저장되는데 0을 포함한 모든 숫자에 부호를 설정 하거나
설정하지 않을 수 있습니다. 대부분에서 연산은 0-0를 동일하게 취급합니다.



isNaN과 isFinite

NaNInfinity는 숫자형에 속하는 특별한 수 입니다. 이 값들은 정상적인 값을 갖는
숫자가 아니기에 실제 값이 있는 수와 구분짓기 위한 특별한 함수들이 존재 합니다.

isNaNisFinite함수 입니다.


isNaN(value)

이 함수는, 인수를 숫자로 변환하고 NaN 인지 확인합니다.

alert( isNaN(NaN) );	// true
alert( isNaN("str") );	// true
alert( NaN==NaN );		// false, NaN 끼리는 값이 다르다.


/* */
isNaN 함수가 필요한 이유
1) 인수 값이 NaN인지 확인 하려면, 인수 == NaN으로 하면 된다고 생각할 수 있습니다. 
2) 하지만 이는 틀렸습니다. NaN은 자신을 포함한 모든 자료형과 값이 다르기에 NaN값을
제대로 확인하기 위해서는 함수를 써야 합니다.

3) 연산을 통해 NaN 값이 나왔지만, == NaN과 비교하면 다르다고 나오기에 이 값이 NaN이
아니라고 착각할 수 있어요

isFinite(value)

이 함수는, 인수를 숫자로 변환하고 변환한 숫자가 NaN | Infinity | -Infinity가 아닌
일반 숫자라면 true를 반환 합니다.

alert( isFinite("15") );	// true
alert( isFinite("str" );	// false, 문자열(str)을 숫자로 변환하면 NaN이기 때문
alert( isFinite(Infinity) ); // false, Infinity 이기 때문

isFinite는 문자열이 일반 숫자인지 검증하는 데에도 사용합니다.

let num = prompt("숫자를 입력하세요", '');	

alert( isFinite(num) );	
// 숫자가 아닌 값을 입력하거나, Infinity, -Infinity를 입력하면 false가 출력 됩니다.

🔔 빈 문자열이나 공백

모든 숫자와 관련된 내장 함수에서 0으로 취급합니다.


🔔 Object.is와 비교 하기

Object.is=== 처럼 값을 비교할 때, 사용되는 특별한 내장 메서드인데, 아래와 같은
두 가지 예시 케이스에선 ===보다 조금 더 신뢰할만한 결과를 보여줍니다.


1) NaN을 대상으로 비교할 떄 : Object.is(NaN, NaN)는 true
2) 0-0이 다르게 취급되어야 할 때 : Object.is(0, -01)는 false


이 두가지 케이스를 제외하고는 a===bOjbect.is(a, b)는 같습니다.
내부 알고리즘에서 두 개의 값을 정확히 비교해야 할 경우에 Object.is를 사용 합니다.
명세서에서 Object.is함수의 비교 방식을 SameValue라고 합니다.


parselnt와 parseFloat

단항 덧셈 연산자+Number()함수를 사용해서 숫자형으로 변환할 때 적용되는 규칙은
엄격합니다. 피연산자가 숫자형이 아니라면 형 변환에 실패합니다.

alert( +"100px" );	// NaN

엄격한 규칙이 적용되지 않는 유일한 예외는 문자열의 처음과 끝에 공백을 무시할 때 입니다.
100px 12pt와 같이 숫자와 단위를 함께 사용하는 경우에는 문자를 제외하고 숫자만 추출해야
하는 경우도 필요합니다. 이때 사용하는 함수가 parselntparseFloat 입니다.


parseInt

이 함수는 문자열에서 숫자를 추출합니다. 숫자를 추출하는 도중 오류가 발생하면, 이미 추출한
숫자중에 정수만 반환합니다.

alert( parseInt("100px") );		// 100
alert( parseInt('12.5em') );	// 12, 정수부분만 반환 합니다.
alert( parseInt('a123') );		// NaN, 좌측부터 숫자를 읽는 데, a는 숫자가 아니기에 NaN 반환
alert( parseInt('15a34') );		// 15, a를 읽는 도주 오류가 발생하기에 읽은 숫자만 반환

parseInt(str, radix) 함수는 두 번째 매개변수를 선택적으로 사용할 수 있습니다.
radix부분엔 원하는 진수를 지정해 줄 때 사용합니다. 따라서 parseInt를 사용하면 16진수
문자열, 2진수 문자열등을 파싱할 수 있습니다.

alert( parseInt('0xff', 16) );	// 255
alert( parseInt('ff', 16) );	// 255, (0x가 없어도 동작합니다)
alert( parseInt('1011', 2) );	// 11
alert( parseInt('2n9c', 36) );	// 123456

parseFloat

parseInt 함수와 동일하게 문자열에서 숫자만 추출합니다. 기능이 똑같으며 추출한 숫자에서
정수와 부동 소수점숫자 모두 반환합니다.

alert( parseFloat("12.5em") );	// 12.5
alert( parseFloat('12.5.6') );	// 12.5, .6에서 오류가 발생해서 12.5까지만 추출
alert( parseInt('a123') ); 		// NaN, a는 숫자가 아니므로 숫자를 읽는 게 중지됩니다.

기타 수학 함수

JS에서 제공하는 내장 객체 Math에는 다양한 수학 관련 함수등이 들어 있다.

Math.random()

01사이의 난수를 반환 합니다. (1은 제외한다)

alert( Math.random() ); 0.21321399123949~~
alert( Math.random() ); 0.41249128412843~~
  

/* */
01사이의 무작의 수를 랜덤으로 표현한다.

Math.max(a, b, c...) / Math.min(a, b, c...)

인수 중에서 최대/최소 값을 반환합니다.

alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2, -1) ); 		// -1

Math.pow(n, power)

n을 power번 거듭제곱한 값을 반환합니다.

alert( Math.pow(2, 10) );	// 2의 10제곱 = 1024



정리

숫자 중에 0이 많이 붙은 큰 숫자는 다음과 같이 표현 합니다.
0의 개수를 e뒤에 추가한다. 123e6123000000을 의미합니다.
e다음에 음수가 오면, 음수의 절댓값 만큼 10을 거듭제곱한 숫자로 수를 나눕니다.
123e-60.000123을 의미 합니다.


JS는 특별한 변환 없이 16진수, 8진수, 2진수를 바로 사용할 수 있게 지원합니다.
parseInt(str, base)를 사용하면 strbase진수로 바꿔줍니다. (2~36진수)
toString(base)은 숫자를 base진수로 바꿔서 문자열로 반환합니다.


12px100pt같은 단위있는 숫자를 출력할 때는, parseIntparseFloat를 사용해서
문자열에서 숫자만 읽고, 도중에 오류가 발생하면 읽은 숫자만 출력합니다.


소수에 대한 어림 수를 구할 때는 Math.floor Math.ceil Math.round num.toFixed()
사용하면 어림 수를 구할 수 있습니다.

소수를 다룰 때는 정밀도 손실이 발생할 수 있으니 당황하지 맙시다.

profile
프론트엔드 개발자가 되기 위한 학습 과정을 정리하는 블로그

0개의 댓글