[Java] 자료형
보통 컴퓨터 저장 용량에 비해 메모리 (RAM)의 용량은 상대적으로 적다. 최소 4GB부터 8, 16, 32, 64GB 등이 있다. 이러한 메모리를 낭비 없이 원활하게 활용하기 위해 자료형을 적절히 잘 사용하는 게 좋다.
정수형
정수를 저장할 때 메모리 낭비를 최소화하기 위해 데이터로 담고자 하는 정수의 범위에 따라 단위가 여러 개로 나뉜다. 자바에서는 각각 1, 2, 4, 8바이트까지 포함할 수 있는 byte, short, int, long 자료형이 존재한다. 자세한 사항은 다음과 같다.
정수 자료형 | 크기 | 자바에서 표현할 수 있는 정수 범위 | 사용되는 메모리 용량 |
---|---|---|---|
byte | 2^8 | -2^7 ~ 2^(7)-1 ⇒ -128 ~ 127 | 1 byte |
short | 2^16 | -2^15~2^(15)-1 ⇒ -32,768 ~ 32,767 | 2 byte (2^16 = (2^8)^2 ) |
int | 2^32 | -2^31 ~ 2^(31) - 1 ⇒ -2,147,483,648 ~ 2,147,483,647 | 4 byte |
long | 2^64 | -2^63 ~ 2^(63) - 1 ⇒ -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 8 byte |
자바에서는 정수 자료형의 범위는 음수, 0, 양수 모두 포함한다.
메모리에서의 데이터 저장 효율성을 위해, 즉 메모리에 저장하고자 하는 데이터의 메모리 사용 용량이 낭비되지 않기 위해 적절한 자료형을 선택하는 것이 좋다. 즉, 예를 들어 5를 메모리에 저장하고자 한다면 long 자료형을 쓸 경우 7 바이트나 낭비된다. 따라서 byte 자료형을 이용하여 저장하는 것이 좋다.
물론 코드 중간에 변수에 담길 정수의 값이 달라질 경우도 고려하면 처음에는 자료형이 표현할 수 있는 값의 범위에 비해 아주 작은 값을 대입해도 상관없다. 예를 들어 short myNum = 5; 라고 하는 경우, 해당 정수값이 자료형 short로 표현하기에는 1 바이트 낭비인 것 같지만, 코드 중간에 myNum = 10000; 으로 변경될 수밖에 없다면 문제 없다.
다음은 정수 자료형을 사용한 예이다.
public class App {
public static void main(String[] args)
{
byte byteNum = 5;
short shortNum = 30000;
int intNum = 200000;
long longNum = 2;
System.out.println(byteNum);
System.out.println(shortNum);
System.out.println(intNum);
System.out.println(longNum);
longNum = 100000;
System.out.println(longNum);
}
}
5
30000
200000
2
100000
문자형
자바에서는 문자 자료형으로 char 자료형만을 제공하며, 해당 자료형의 크기는 2 byte이다. 자바에서의 char 자료형은 다음의 특징을 가진다.
- char 자료형 변수에는 문자 한 글자만 대입할 수 있다. (해당 자료형의 크기가 2바이트 뿐이기 때문)
- 자바에서는 문자를 표현할 때 작은 따옴표를 사용한다.
char alphabet = ‘C’;
(여러 글자들을 모은 문자열을 표현하고자 한다면 큰 따옴표(””)를 이용하며, 문자열을 다루는 String 자료형이 따로 있다. String 자료형은 추후 다른 문서에서 다룰 예정)
문자를 메모리 상에 저장할 때에는 유니코드(Unicode) 또는 아스키코드 (ASCII)의 대조표를 통해 각 문자마다 할당된 숫자값을 읽어들여와 해당 숫자값을 이진수로 변환하여 저장하는 방식이다. 예를 들어, 아스키코드에서 영대문자 ‘C’는 십진수로 67이라는 값이 할당되어 있다. 자바에서는 이를 2진수인 1000011로 변환하여 이 2진수를 메모리에 저장하는 것이다. 이 때, char 자료형은 2바이트이므로, 앞의 1바이트는 모두 0이 채워진 상태가 된다. 즉, 문자 ‘C’를 char 자료형을 이용하여 메모리에 저장하면 0000 0000 0100 0011 이 된다.
참고로, 문자를 코드표를 통해 이진수로 변환하는 것을 인코딩(encoding), 반대로 이진수를 코드표를 통해 문자로 변환하는 것을 디코딩(decoding)이라 한다.
유니코드, 아스키코드, 인코딩, 디코딩에 대한 자세한 사항은 컴퓨터가 문자를 다루는 방식, unicode, encode & decode 페이지 참고.
다음은 문자 ‘C’를 자바에서 표현하는 방법에 관한 예제이다. 위에서부터 차례대로 문자 그대로, 10진법, 2진법, 16진법으로 문자 ‘C’를 표현한 것이다.
public class App {
public static void main(String[] args)
{
char alp1 = 'C';
char alp2 = 67; // 10진수 값
char alp3 = 0b0000000001000011; // 2진수 값
char alp4 = 0x43; // 16진수 값
System.out.println(alp1);
System.out.println(alp2);
System.out.println(alp3);
System.out.println(alp4);
}
}
C
C
C
C
참고로 자바에서는 2진법으로 표현된 수 앞에 접두어 0b를, 16진법은 0x를 붙인다.
논리형
참, 거짓을 나타내는 자료형으로, boolean 자료형이 있으며, 1바이트를 차지한다. 이 자료형에는 true, false 이 두 가지 값만 가진다. 코드에서 사용할 때에는 true, false 키워드를 직접 사용할 수도 있고, 연산의 결과값이 boolean일 경우도 있다.
public class App {
public static void main(String[] args)
{
boolean tf = true;
boolean tf2 = false;
boolean result = (2 < 3);
System.out.println(tf);
System.out.println(tf2);
System.out.println(result);
System.out.println(21 < 12);
}
}
true
false
true
false
실수형
컴퓨터에서는 실수 데이터를 처리, 저장할 때에는 정수와는 다른 방식을 사용한다. 실수는 정수와 달리 소수점이 있기 때문이다.
실수를 정수부와 실수부로 나누고, 32비트 중 맨 앞 1비트를 부호 비트, 그 뒤의 15비트를 정수부, 그 뒤의 16비트를 소수부를 저장하는데에 사용하는 방식을 고정 소수점(fixed point) 방식이라 한다. 그런데 이 방식은 정수부, 소수부를 차지하는 비트 수가 무한대로 많은 실수를 다 표현하기에는 턱없이 부족하다. 그렇기에 현대에는 고정 소수점이 아닌 부동 소수점 방식이 사용된다.
예를 들어, 10진수인 실수 118.625가 있다면 이를 2진수로 바꾸면 110110.101이 된다. 여기서 소수점을 0이 아닌 맨 앞자리 수의 바로 뒤에 위치 시키고 나머지는 지수로 표현하면 \(1.110110101\ X \ 2^6\) 이 된다. 이 때, 1.110110101을 가수부, 2의 6제곱의 6을 지수부라 하며, 가수와 지수를 이용해 수를 표현하도록 변환하는 것을 정규화라고 한다. 또한, 실수를 표현할 때 위와 같이 가수부와 지수부를 이용하여 표현하는 방식인 부동 소수점(floating point) 방식이 있다. IEEE 754 표준에 따르면, float 형 부동 소수점 방식에는 총 32비트를 사용하며, 그 중 맨 앞의 1비트를 부호로, 그 뒤의 8비트를 지수부, 그 뒤의 23비트를 가수부를 저장하는데에 사용한다. double형의 경우 총 64비트를 사용하며, 맨 앞 부호는 1비트, 그 뒤의 11비트는 지수부, 그 뒤의 52비트를 가수부로 사용한다.
사진 1. 부동 소수점 방식을 이용하여 실수를 비트에 저장하는 방식. [2] 참조.
10진법 실수를 2진법으로 정규화한 뒤, 이를 비트에 저장할 때 사용하는 공식은 다음과 같다. (float형 기준)
\[\pm (1.m) \times 2^{e-127}\]위 식에서 m은 가수부, e는 지수부이다.
부동 소수점 방식은 고정 소수점 방식에 비해 표현할 수 있는 실수의 범위가 늘어난다.
(10진수 실수를 2진수로 변환하는 과정과, 부동 소수점에 관한 자세한 사항은 숫자 페이지 참고)
그런데 부동 소수점 방식을 사용하면 실수를 항상 근사치로 표현할 수밖에 없어 오차가 생긴다는 단점이 발생한다. 위 공식의 m 또는 e에 어떤 값을 대입하건 정수부 또는 소수부를 0으로 만들 수가 없기 때문이다. (지수는 2의 몇제곱을 하더라도 절대 0을 만들 수 없다) 이러한 이유로, 컴퓨터는 실수를 정확하게 표현할 수 없고, 근사치로만 표현할 수 있다. 다음은 부동 소수점 오차를 보여주는 예제이다.
public class App {
public static void main(String[] args)
{
// case 1
double n1 = 1.0000001;
double n2 = 2.0000001;
System.out.println(n1 + n2);
// case 2
double total = 0.0;
for (int i = 0; i < 100; i = i + 1) {
total = total + 0.1;
}
System.out.println(total);
}
}
3.0000001999999997
9.99999999999998
자바에서 실수형은 다음이 존재한다.
- float, 크기 4바이트
- double, 크기 8바이트
double 형이 실수형의 기본형으로 설정되며, float형을 쓰고자 한다면 float형을 따로 명시해줘야 한다.
번외: 부동소수점 오차의 누적
다음의 코드를 보자.
public class FloatError {
public static void main(String[] args) {
double a = 0.1;
double b = 100;
System.out.println(a * b);
}
}
10.0
public class FloatError {
public static void main(String[] args) {
double total = 0.0;
for (int i = 0; i < 100; i++) {
total = total + 0.1;
}
System.out.print(total);
}
}
9.99999999999998
위의 두 예제는 사실 같다. 0.1에 바로 100을 곱하나, 0.1을 100번 더하나 모두 10.0이 나와야 정상이다. 설령 부동 소수점 오차를 고려한다 해도, 그렇다면 두 예제 모두 10.0이 아닌 근사치가 나와야 하는데, 전자의 경우 10.0이 그대로 나온다. 왜 이런 결과가 나오는 것일까?
우선 10진수 실수 0.1이 컴퓨터 내 메모리 상에 저장되기 위해선 해당 실수를 2진수로 변환해야 한다. 그런데 10진수 실수 0.1을 2진수로 변환하면 0.00011001100110011… 과 같은 무한 소수가 되어 끝이 정해지지 않는다. 이러한 이유로, 공간에 한계가 있는 메모리 공간에 저장될 때에는 어느 정도까지는 절삭하여 저장해야 하는데, 바로 여기서 오차가 발생한다. 즉, 컴퓨터는 0.1이라는 실수를 정확한 값이 아닌 근사치로 표현할 수밖에 없다.
이 상황에서, 예제 5-1은 0.1에 100이라는 수를 곱하는 연산을 한다. 이 과정에서 연산은 딱 한 번만 진행되기에 오차가 매우 작아 10.0이라고 표시된다.
반면, 예제 5-2의 경우, 연산을 100번이나 거치기 때문에, 오차도 100번씩 누적되게 된다. 이렇게 오차가 누적되어 더 커졌기 때문에 정확한 값 10.0이 아닌 9.99999999999998 이 나오게 된 것이다.
References
[1] 코딩교육 티씨피스쿨
[2] IEEE 754
[3] 윤성우, “윤성우의 열혈 C 프로그래밍”, (오렌지미디어, 2010), p86~92
[4] 이재환, “이재환의 자바 프로그래밍 입문”, (골든래빗, 2021)
This content is licensed under
CC BY-NC 4.0
댓글남기기