THE DEVLOG

scribbly.

CS공부: 데이터 스트럭처

2023.01.24 22:57:46

자료형에 대해 64비트 부동소수점의 원리부터 등등등 매우 열심히 적어놨는데,
임시 저장이 꼬이면서 다 날아갔다.

흑흑 ㅠㅠ
후...너희들은 벨로그 쓰지 마라.

다시 기억나는대로 작성해서 올리고자 한다. (때문에 출처가 중간중간 누락되었다. ㅠㅠ 출처 다시 보려고 했는데.)

자료형

데이터 스트럭처를 공부하기 전에 간단한 자료형을 알아보자.

컴퓨터는 2진법으로 이루어져 있다.
0000 0000은 0을 의미하고
0000 0001은 1을 의미하고
0000 0010은 2의 1승, 즉 2를 의미한다.
0000 0011은 2의 1승+1을 의미한다.

표현할 수 있는 비트가 위처럼 8개 묶여있으면 2의 8승 개(42억9천4백96만7천2백9십6개)의 숫자를 표현할 수 있다. (0부터 4,294,967,295까지)

2의 거듭 제곱비고
2^01
2^12
2^24
2^381Byte
2^416
2^532
2^664
2^7128
2^8256
2^9512
2^101,0241kiB
2^112,048
2^124,096
2^138,192
2^1416,384
2^1532,768
2^1665,536
2^17131,072
2^18262,144
2^19524,288
2^201,048,5761MiB
2^212,097,152
2^224,194,304
2^238,388,608
2^2416,777,216
2^2533,554,432
2^2667,108,864
2^27134,217,728
2^28268,435,456
2^29536,870,912
2^301,073,741,8241GiB
2^312,147,483,648
2^324,294,967,2964GiB

30비트는 1기비바이트이다.
1기비비트의 4배는 4기비비트, 2의 32승이다.

32비트 컴퓨터에서는 램을 4기가 바이트 밖에 인식 못한다. 메모리 각각의 공간을 컴퓨터의 각 비트와 물리적으로 연결하게 되는데, 표현할 수 있는 숫자가 43억개 가량 밖에 되지 못하므로, 램이 더 커도 43억개의 칸 밖에 사용하지 못하는 것이다. 실제로는 컴퓨터 구조상 일부 비트는 다른 하드웨어와 연결되므로 4기가 보다 적은 숫자를 사용하게 된다. 그 밖에 32비트 기반의 많은 시스템이 '4기가 바이트'를 기준으로 하는 것을 볼 수 있다.(가령 파일을 4기가 바이트까지 밖에 저장하지 못하는 DVD)

32비트 정수형

정수형은 4,294,967,296개를 저장할 수 있다.
이때 가장 앞의 첫번째 비트는 부호이다.

−2,147,483,648부터 2,147,483,647까지를 표현한다.

정수의 표현법에 대해 8비트로 간단히 보자면

0000 0001은 1을 의미한다.
0000 0000은 0을 의미한다.
1111 1111은 음수 부호가 붙었는데, -0이 아니라 -1이다.

-0이 아닌 이유는

  1. 비트 하나를 아껴서 한 숫자를 더 많이 표현할 수 있다.
  2. +0과 -0으로 0이 두개가 존재하는 경우 연산이 꼬일 수 있다.
  3. '보수 연산'을 하기 위함이다.

보수란 모든 자리수가 특정 수가 되도록 만들기 위해 보충해줘야 하는 값이다.
0001의 1의 보수는
1111 1110이다.
0001과 1110을 더하면 1111이다.

0001의 2의 보수는? 모든 자리수가 0이 되면 된다. 이는 1의 보수에 1을 더한 값과 같다.
0001의 2의 보수는 1111이다.
더하면 1 0000이 됨을 확인할 수 있다. 4비트이므로 앞의 1을 버리면 0000. 즉 1+(-1)=0 이라는 결과가 출력됐다.

이제 보수 연산의 예시를 보자.

9-4는
9 + (-4)로 표현된다.
이는

0000 1001
+ 1111 1100 (-4)

로 표현될 수 있다.

결과는 1 0000 0101인데 8비트로 연산하였으므로 가장 앞의 1이 버려진다.
0000 0101, 즉 5가 잘 나온다.

자바스크립트에서는 비트연산을 할 시에 32비트 정수를 반환한다.

64비트 실수형

실수형은 부호 1비트와 지수부, 가수부로 나뉜다.
주로 부동소수점 방식이 쓰이는데, 부동소수점 방식은 '가수부를 정규화하고 지수부로 소수점의 위치를 이동시킨다'는 점이다. 그래서 부동(떠서 움직임)소수점이라 불린다.

먼저 아래의 예시를 보자

  1. 10진수를 2진수로 바꾸기
7.75 = 4 + 2 + 1 + 0.5 + 0.25
= 2^2 + 2^1 + 2^0 + 2^(-1) + 2^(2)
= 0b111.11
  1. 정규화 : 정규화란 가수부가 밑수(위의 예시는 2진법이므로 2)보다 작도록 만든 후, 지수를 곱해주는 형태를 의미한다.
111.11 = 1.1111 * 2^2

정규화된 값의 정수부는 항상 1이므로 제외한다. 소숫점 부분만을 유효숫자(significand)로하여 기수부에 기입할 예정이다.

  1. 지수부와 bias : 지수부를 표현할 때에는 바이어스 표현법을 사용한다.
    가령 8비트를 예로들면 0000 0000은 0이 아니라 8비트로 표현할 수 있는 가장 작은 음수, 즉 -127을 의미한다. 이러한 bias표기법은 0000 0000은 가장 작은 수(-127)를 의미하고, 1111 1111은 가장 큰 수(128)을 의미하기 때문에 지수를 일관되게 표현할 수 있으며, 지수부만 따로 보수 연산하는 경우가 때문이다.
    정수형을 바이어스 표기법으로 변환하려면 바이어스를 구해야 한다. K비트의 바이어스는 (2^(k-1)-1)이다. 즉 8비트일 때에는 127이다. 이렇게 구해진 바이어스에 지수를 더한 후 비트로 변환하면 된다. 만일 지수가 2^2라면 127+2를 비트로 변환한 값, 즉 1000 0001이 된다.

위의 예시에서는 4비트만큼을 지수부로 선언해주고자 한다. 4비트의 바이어스는 2^(3)-1, 즉 7이다. 지수는 2이다. 9를 4비트로 변환하면 0b1001이다.

  1. 가수부
    지금까지 부호 1비트(0)
    지수부 4비트(1001)를 사용했다.
    가수부 3비트가 남았다.

정규화된 값에서 유효숫자 1111을 얻었다.
이를 앞에서부터 3비트 입력해준다.

결과 0 1001 111

해당 결과를 10진법으로 표현하면

1.111 * 2^2 = 111.1 = 2^2 + 2^1 + 2^0 + 2^(-1) = 7.5

기존 값의 7.75와 0.25(즉, 2^(-2)) 만큼의 오차가 났다.
이러한 오차가 부동 소수점 오류의 원인이다.

단정도와 배정도

float는 단정도(single precision) 32비트 부동소수점 방식을 의미한다. 부호 1비트, 지수부 8비트, 가수부 23비트로 이루어져 있다.

double은 64비트 부동소수점 방식을 의미한다. 부호 1비트, 지수부 11비트, 가수부 52비트로 이루어져 있다.

자바스크립트의 64비트 부동소수점

자바스크립트는 배정도이다.
Number.MAX_SAFE_INTEGER : 자바스크립트가 표현할 수 있는 가장 큰 정수는 9007199254740991, 즉 2^53-1이다.

Nunber.MIM_SAVE_INTEGER : 가장 작은 정수는 -9007199254740991, 즉 -(2^53-1) 이다.

Number.EPSILON : 1을 정규화한 수 1.00000000000000000000000000000000000000000000000000000 * 2^0과 1의 바로 다음 수 1.00000000000000000000000000000000000000000000000000001 ^ 2^0 (0 51개 + 1비트) 의 차이를 EPSILON이라 한다. 이는 2^(-52)에 해당하는 값이며, 지수표현식으로는2.2204460492503130808472633361816E-16이라 표현한다.

엡실론과 부동소수점 오류

console.log(0.1 + 0.2 === 0.3); 이를 출력하면 결과는 false로 나온다. 0.1과 0.2을 64비트 부동소수점으로 표현하면 0.0001100110011001100110011001100110011001100110011001101, 0.001100110011001100110011001100110011001100110011001101과 같이 1100이 무한히 반복된다. 그로 인해 0.1+0.2는 0.30000000000000004가 나오는 것이다.

이러한 문제를 해결하기 위해 ES6에서 도입된 EPSILON을 사용할 수 있다.

console.log(Math.abs((0.1+0.2) - 0.3) < Number.EPSILON;) // true

A-B의 절대값이 엡실론보다 작으면 A=B라고 볼 수 있다.

문자형

ASCII 코드

ASCII (American Standard Code for Information Interchange, 미국 정보 교환 표준 부호)

8비트 중 7비트를 사용하여 128글자를 표현할 수 있다. 나머지 1비트는 Parity Bit라는 일종의 CheckSum으로서, 7개의 비트 중 1의 갯수가 홀수이면 Parity Bit는 1, 짝수이면 Parity Bit는 0으로 하여 8비트 중 1의 갯수를 항상 짝수로 만들어 전송 중 신호의 변질을 체크하는 역할이었다고 한다.

10진수부호10진수부호10진수부호10진수부호
0320568080P104h
033!0579081Q105i
034"058:082R106j
035#059;083S107k
036$060<084T108l
037%061=085U109m
038&062>086V110n
039'063?087W111o
040(064@088X112p
041)065A089Y113q
042*066B090Z114r
043+067C091[115s
044,068D092\116t
045-069E093]117u
046.070F094^118v
047/071G095_119w
0480072H096`120x
0491073I097a121y
0502074J098b122z
0513075K099c123{
0524076L100d124
0535077M101e125}
0546078N102f126~
0557079O103g

영문 알파벳 26자 중 대문자 A는 65번, 대문자 Z는 90번, 소문자 a는 97번, 소문자 z는 122번이다.

유니코드

유니코드는 4바이트, 즉 32비트로 문자를 표현한다. 아스키코드가 8비트, 즉 1바이트 였던 것에 비하여 용량이 크다.
이를 더 적은 수의 비트로 줄이는데, 이를 '인코딩'이라 한다.

UTF-16

16비트를 한 단위로 한다.
즉 문자는 2바이트 혹은 4바이트로 표현된다.
한글의 경우 3바이트 유니코드를 2바이트로 표현할 수 있기에 유리하지만, 알파뱃의 경우 1바이트 아스키 코드를 2바이트로 표현해야 하기에 불리하다.

한편 2^16은 65,536개의 문자를 표현할 수 있음을 의미하는데, 이 문자에 표함되지 않으면 4바이트를 사용하는 하위 인코딩으로 넘어간다. 하위 인코딩도 제각각이고 각각의 비트를 어떻게 정렬할 것인가 하는 엔디언 문제도 제각각이라 통합되지 않는 문제들이 발생한다.

UTF-8

8비트를 한 단위로 한다. 즉 한 문자는 1바이트, 2바이트 혹은 3바이트로 표현된다. (4바이트는 아직 쓰지 않는다.)

1바이트 영역은 아스키 코드와 동일하다.
한글이나 한문 등의 문자가 3바이트로 표현되는 점에서는 다소 불리하지만, 전 세계의 언어들을 단일 인코딩으로 지원할 수 있다는 점에서 현재 가장 널리 쓰이는 인코딩이다.

자바스크립트와 UTF-16

자바스크립트의 문자형은 인코더와 무관하게 UTF-16을 반환하며, 0~65,535 코드를 지원한다 (이 숫자를 넘어가면 다시 처음부터 센다.)

"A".charCodeAt() //65
"OOA".charCodeAt(2) // 65
"z".charCodeAt() - "a".charCodeAt() //25
String.fromCharCode(65, 66, 67) //"ABC"
String.fromCharCode(65601) // "A"
//16진수도 지원한다
String.fromCharCode(0x2014); // returns "—"
String.fromCharCode(0x12014); // also returns "—"; the digit 1 is truncated and ignored