산술 연산자
기본 연산 단위
@정수 byte와 int
1
2
3
4
5
6
7
8
9
// 리터럴에 대한 연산
byte b;
b = 5 + 6;
// 변수에 대한 연산
byte x = 5, y = 6, z;
z = x;
z = y;
//z = x + y; //컴파일 오류
자바의 최소 연산 단위는 4바이트이다. 리터럴끼리의 연산은 결과도 리터럴로 간주하기 때문에 결과 값이 변수 범위 내의 값이면 저장을 허락한다. 변수에 저장된 값과 달리 리터럴 값은 컴파일 단계에서 값을 확인할 수 있기 때문이다.
반면, 변수 끼리의 연산은 컴파일 단계에서 값을 확인할 수 없다. 자바는 정수 변수에 대해 산술 연산을 할 때, 변수의 값이 4바이트보다 작다면, 임시 4바이트 정수 메모리를 만들고 그 메모리에 값을 담은 후에 연산을 수행한다. 위의 코드에서 x + y를 바로 실행하는 것이 아니라, 임의의 4바이트 정수 메모리를 만든 다음에 그 메모리에 x와 y의 값을 넣고 연산을 수행한다. 연산의 결과값도 4바이트이다. 따라서 4바이트 값을 1바이트 메모리에 넣지 못하기 때문에 컴파일 오류가 발생하는 것이다.
다시 말해, int보다 작은 크기의 메모리값을 다룰 때는 내부적으로 int로 자동 형변환을 수행한 다음에 연산을 수행하는데, 이를 암시적 형변환(implicit type conversion) 이라 한다.
내부적으로 자동형변환을 하는 것을 암시적 형변환이라고 한다.
byte + byte = int
short + short = int
byte + short = int
연산의 결과 타입
@정수와 부동소수점
1
2
3
4
5
6
7
8
9
10
int i = 5;
int j = 2;
// 형변환을 하지 않고 연산하였을 때
float r = i / j;
System.out.println(r); //2.0
// 형변환을 하고 연산하였을 때
r = (float)i / (float)j;
System.out.println(r); //2.5 (성공!)
@정수 int와 long
1
2
3
4
5
6
7
8
9
10
11
12
int x = Integer.MAX_VALUE; // 약 21억
int y = Integer.MAX_VALUE; // 약 21억
int r1 = x + y; // 오버플로우 발생
System.out.println(r1) // -2
long r2 = x + y; // int 결과값을 long 타입 메모리에 저장
System.out.println(r2); // -2
// 형변환!
r2 = (long)x + (long)y;
System.out.println(r2) // 약 42억 (성공!)
@부동소수점 float과 double
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
float f1 = 987.6543f;
float f2 = 1.111111f;
// 987.6543 - 1.1111111 = 988.765411
float r1 = f1 + f2; //유효자리수 초과 => 잘림
System.out.println(r1); //988.7654
double r2 = f1 + f2; //float 결과값을 double 타입 메모리에 저장
System.out.println(r2); //988.765380859375
//뒤의 이상한 숫자는 저장 전 float 값이 되면서 일부 왜곡 (IEEE의 이진수 변환 문제)
// 부동소수점의 경우 이진법 변환 문제 때문에
// 연산을 할 것 같으면 처음부터 double 변수를 사용하는 것이 좋다.
double d1 = 987.6543;
double d2 = 1.111111;
double r3 = d1 + d2;
System.out.println(r3); //988.7654110000001
// 극한의 작은 수 처리 필요 (부동소수점 이진수 변환 문제)
연산 결과는 피연산자의 타입과 같다.
int + int = int
long + long = long
float + float = float
double + double = double
첫번째의 코드에서 int타입의 i 와 j를 연산하면 결과값도 int타입이 된다. 따라서 2.5가 아니라 2가 되고, 이를 float에 저장하면 2.0이 된다. float타입의 2.5를 리턴하고 싶다면 피연산자를 해당 타입으로 바꿔야 한다. 이를 **명시적 형변환 **(type conversion, type casting)이라고 한다.
세번째의 코드의 마지막 출력값(988.7654110000001)은 기대하는 값(988.765411)과 극한의 작은 오차값이 있다. 이 극한의 작은 수 처리에 대해서는 따로 포스팅을 하였다.
형변환
암시적 형변환과 명시적 형변환에 더 자세히 알아보자.
형변환은 변수나 리터럴을 다른 타입의 값으로 바꾸는 것을 말한다. 이때, 원래 변수의 타입을 바꾸는 것이 아니라, 변수에 들어 있는 값을 꺼내 지정된 타입의 임시 메모리를 만들어 저장한다. 즉, 변수에 들어있는 값에는 영향이 없다.
암시적 형변환 (implicit type conversion)
형변환에는 JVM이 내부 규칙에 따라 자동으로 처리해주는 암시적 형변환이 있다.
암시적 형변환 순서
byte,short,char => int => long => float => double
단, 연산 우선순위에 따라 계산하는 순간에 암시적 형변환이 일어난다.
정수와 부동소수점에 대해서만 암시적 형변환이 일어난다.
1
2
3
4
5
6
7
8
byte b = 9;
short s = 8;
int i = 7;
long l = 6;
float f = 5.5f;
double d = 4.4;
boolean bool = true;
char c = 1;
@정수 타입
- byte + byte = int
- 연산을 하기 전에 byte 값이 int로 암시적 형변환 된다.
byte r1 = b + b;
=> 컴파일 오류
- short + short = int
- 연산을 하기 전에 short 값이 int로 암시적 형변환 된다.
short r1 = s + s;
=> 컴파일 오류
- byte + short = int
- 연산을 하기 전에 byte와 short 값이 int로 암시적 형변환 된다.
short r3 = b + s;
=> 컴파일 오류
- byte + int = int
- 연산을 하기 전에 byte 값이 int로 암시적 형변환 된다.
int r4 = b + i;
=> OK!!
- short + int = int
- 연산을 하기 전에 short 값이 int로 암시적 형변환 된다.
int r4 = s + i;
=> OK!!
- int + long = long
- 연산을 하기 전에 int가 long으로 암시적 형변환 된다.
int r6 = i + l;
=> 컴파일 오류
@정수 타입 + 실수 타입
- int + float = float
- long + float = float
@실수 타입
- float + double = double
@정리
- byte + short + int + long + float + double = double
- float + int + long = float
- boolean + int = 컴파일 오류!
- 산술연산자는 정수타입(byte, short, char, int , long)과 부동소수점 타입(float, double)에 대해서만 실행할 수 있다.
int r12 = bool + i
=> 컴파일 오류
명시적 형변환
1
2
3
4
byte b;
//b = 257; // 컴파일 오류
b = (byte) 257; // OK!
System.out.println(b); // 1
위의 코드에서 메모리에 들어가기 큰 값이기 때문에 오버플로우가 발생해 값이 잘렸다. 큰 메모리의 값을 작은 메모리에 넣으려고 형변환을 사용하기도 하는데, 형변환하더라도(작은 메모리에 넣더라도) 값이 소실되지 않을 때만 명시적 형변환을 지시하는 것이 좋다.