2014년 9월 3일 수요일

Java 연산자(산술연산자-사칙연산자)

3. 산술연산자

산술 연산자인 사칙 연산자(+ - * /), 나머지 연산자(%), 쉬프트 연산자(<<,>>,>>>)는 모두 두개의 피연산자를 취하는 이향 연산자입니다.
모든 이항 연산자는 연살을 수행하기 전에
 - 크기가 4byte이하인 자료형을 int형으로 변환한다.(byte, char, short -> int)
- 피연산자들의 타입을 서로 일치시킵니다.

3.1 사칙 연산자 + - * /

사칙 연산자는 프로그래밍에서 가장 많이 쓰이는 연사자 입니다. 보통 사람들이 이미 알고 있듯이 곱셈(*),나눗셈(/),나머지(%) 연산자가 덧셈(+),뺄셈(-)연산자 보다 우선수위가 높습니다.
1. int형(4byte)보다 크기가 작은 자료형은 int형으로 형변환 후에 연산을 수행합니다.
    byte + short -> int + int -> int
2. 두개의 피연산자 중 자료형의 표현범위가 큰 쪽에 맞춰서 형변환 후 수행합니다.
    int + float -> float + float -> float
3. 정수형 간의 나눗셈에서 0으로 나누는 것은 금지되어 있습니다.


ex)
byte + byte = int + int -> int
byte + short = int + int -> int
char + char = int + int -> int

float + int -> float + float -> float
long + float -> float + float -> float
float + double -> double + double -> double 

피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없습니다. 만일 0으로 나누면, 컴파일은 정상적으로 되지만 실행 시 오류가 발생합니다. 하지만 부동소수점값인 0.0f, 0.0d로 나누는 것은 가능하지만 그 결과는 NaN(Not A Number, 숫자 아님)입니다.


public class Operator8 {

public static void main(String[] args) {
byte a = 10;
byte b = 20;
byte c = (byte) (a + b);
System.out.println(c);
}
}

실행결과


30

7번째 줄을 보면 형변환을 하였는데, a와 b는 int형보다 작은 byte형이기 때문에 연산자'+'는 이 두 개의 피연산자들의 자료형을 int형으로 변환한 다음 덧셈을 수행합니다. 그래서 'a+b'의 연산결과는 byte형이 아닌 int형이기 때문에 7번째 줄에 형변환을 꼭 붙여야 합니다. 그렇지 않으면 에러가 발생합니다.
크기가 작은 자료형의 값을 큰 자료형의 변수에 저장할 때는 자동으로 형변환되지만, 큰 자료형의 값을 작은 자료형 변수에 저장하려면 캐스트 연산자를 사용해서 변환해주어야 한다.
(주의) byte c = (byte)a+b;와 같이 문장을 써도 에러가 발생합니다. 그 이유는 형변환 연산자는 단항 연산자이므로 연산 순위가 이항 연산자인 덧셈 연산자보다 높습니다. 그렇기 때문에
(byte)a가 먼저 수행되고 다음 덧셈 연산이 수행됩니다. 즉 a만 byte형으로 형변환된 후 int형인 b와 연산을 합니다.

public class Operator9 {

public static void main(String[] args) {
byte a = 30;
byte b = 10;
byte c = (byte) (a*b);
System.out.println(c);
}
}

실행결과

44

이 예제를 실행하면 44가 화면에 출력되는데, 연산결과는 300이지만, 앞의 형변환에서 배웠듯이 큰 자료형에서 작은 자료형으로의 변환을 하면 데이터 손실이 발생하므로 원하는 결과값이 안나옵니다. 300은 byte의 범위를 넘기 때문에 byte형으로 변환하면 데이터 손실이 발생하여 44가 출력이 됩니다. byte형의 범위인 -128~127의 범위를 넘는 int형의 값을 byte형으로 변환하면, 원래의 값은 손실이 되고, byte형의 범위 중 한 값을 가지게 됩니다. 따라서 원하는 결과 값을 얻기 위해서는 충분히 큰 자료형을 사용해야합니다.

public class Operator10 {

public static void main(String[] args) {

int a = 1000000;
int b = 1000000;
long c = a * b;
System.out.println(c);


}
}

실행결과

-727379968

a*b의 결과를 long형 변수c에 저장하기 때문에 2*10^12를 저장하기에 충분하므로 '20000000000000'이 출력될 것 같지만, 결과는 예상과 다르게 나왔습니다. 이유는 int형과 int형의 연산결과는 int형이기 때문입니다. a*b의 결과가 이미 int형 값(-727379968)이기 때문에 long형으로 자동 변환이 되어서 long형 변수인 c에 저장되어도 결과는 같습니다. 따라서 원하는 값을 얻기 위해서는 변수 a, b의 타입을 long으로 바꾸어야 합니다.

public class Operator11 {

public static void main(String[] args) {

long a = 1000000 * 1000000;
long b = 1000000 * 1000000L;

System.out.println(a);
System.out.println(b);

}
}

실행결과

-727379968
1000000000000

1000000 * 1000000은 int와 int의 연산이기 때문에 그 결과가 int입니다. 그래서 10^6*10^6의 곱셈연산은 10^12이기 때문에 int의 최대값인 대략 2*10^9을 넘기 때문에 오버플로우가 발생해서 -727379968를 결과로 얻습니다.
1000000 * 1000000L은 int와 long의 연산이기 때문에 그 결과가 long입니다. long은 10^12을 저장하기에는 충분한 자료형이므로 원하는 결과를 얻을 수 있습니다.


public class Operator12 {

public static void main(String[] args) {

long a = 1000000 * 1000000 / 1000000;
long b = 1000000 / 1000000 * 1000000;

System.out.println(a);
System.out.println(b);

}
}

실행결과

-727
1000000

곱셉 연산을 먼저 하면 int의 범위를 넘어서기 때문에 워하는 결과가 나오지 않습니다.
위와 같이 곱셈과 나눗셈의 연산 중에 무엇을 먼저 쓰느냐에 따라서 결과가 달르게 나옵니다. 

public class Operator13 {

public static void main(String[] args) {

char ch1 = 'a';
char ch2 = ch1;
char ch3 = ' ';

int i = ch1 + 1;

ch3 = (char)(ch1+1);
ch2++;
ch2++;

System.out.println("i="+i);
System.out.println("ch2="+ch2);
System.out.println("ch3="+ch3);

}
}

실행결과

i=98
ch2=c
ch3=b

ch1을 계산할 때, ch1이 char형이므로 int형으로 변환한 후 덧셈연산을 수행하게 되기 때문에 c1에 저장되어 잇는 코드값이 변환되어 int형 값이 된다. 따라서 ch1+1은 97+1이 되어 int형 변수 i에는 98이 저장이 됩니다.
ch2++은 형변환없이 ch2에 저장되어 있는 값을 1 증가시키므로, 예제에서는 원래 저장되어 있던 값인 97을 1씩 두 번 증가하여 99가 됩니다. 코드값이 10진수로 99인 문자는 'c'이므로 c2를 출력하면 c가 출력됩니다.


public class Operator14 {

public static void main(String[] args) {


char ch1 = 'a';
// char ch2 = ch1+a;  //컴파일 에러 발생
char ch2 = 'a'+1;       //컴파일 에러 없음

System.out.println(ch2);

}
}

실행결과

b

덧셈 연사자와 같은 이항 연산자는 int보다 작은 타입의 피연사자를 int로 자동형변환한도 했는데 왜 에러가 발생하지 않는 것일까?
'a+1'이 리터널 간의 연산이기 때문이다. 상수 또는 리터널 간의 연산은 실행과정동안 변한느 값이 아니기 때문에, 컴파일 할때 컴파일러가 계산해서 그 결과로 대체함으로써 코드를 보다 효율적으로 만든다.
컴파일러가 미리 덧셈연산을 수행하기 때문에 실행 시에는 덧셈 연산이 수행되징 ㅏㄶ는다. 그저 덧셈연산결과인 문자 b를 ch2에 저장한다.

컴파일 전의 코드               컴파일 후의 코드
char ch2 = 'a'+1'              char ch2 = 'b';
int a = 60*60*60;              int a = 216000;

하지만 에러가 발생하는 부분은 수식에 변수가 들어가 있다. 변수가 들어가 있는 경우에는 컴파일러가 미리 계산을 할 수 없기 때문에 아래의 오른쪽 코드와 같이 형변화을 해주어야 한다.
즉 char ch2 = c1 + 1; -> char ch2 = (char)(c1 + 1);

public class Operator15 {

public static void main(String[] args) {

char ch = 'a';
for(int i=0; i<26; i++){       //출력() 안의 문장을 26번 반복한다.
System.out.print(ch++);   //'a'부터 26개의 문자를 출력한다.
}
System.out.println();         //줄바꿈
ch = 'A';
for(int i=0;i<26;i++){
System.out.print(ch++);
}
System.out.println(); 
ch = '0';
for(int i=0;i<10;i++){
System.out.print(ch++);
}
System.out.println(); 
}
}

실행결과

abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789

문자 a부터 z까지 26개의 문자를 출력하고, 문자A부터 Z까지 26개의 문자를 출력하며, 0부터 9까지 10개의 숫자를 출력합니다. 문자 a의 코드값은 10진수로 97, b의 코드값은 98.... z의 코드값은 122입니다. 문자 A의 코드값은 10진수로 65...Z의 코드값은 90입니다. 그리고 문자 0의 코드값은 48입니다.
이를 이용하여 대문자를 소문자로, 소문자를 대문자로 변환하는 프로그램을 작성할 수 있습니다.

public class Operator1 {

public static void main(String[] args) {
char ch1 = 'a';
char ch2 = (char)(ch1 - 32);
System.out.println(ch2);
}
}

실행결과

A


a의 코드값이 97이고, A의 코드값이 65입니다. a보다 32가 적으므로 소문자 a의 코드값에서 32를 빼면 대문자 A가 출력되고, 반대로 A의 코드값에 32를 더하면 소문자 a가 출력이 됩니다.

public class Operator18 
{

public static void main(String[] args) {
float Pi = 3.141592f;
float intPi = (int)(Pi*1000)/1000f;
System.out.println(intPi);
}
}

실행결과

3.141

int형 간의 나눗셈 int/int를 수행하면 float나 double이 아닌 int다. 그리고 나눗셈의 결과는 반올림이 아니라 버린다. 1/2의 결과는 1.5가 아니라 1이다.
위의 예제는 누눗셈 연산자의 성지을 이용해서 실수형 변수 pi의 값을 소수점을 셋째자리가지만 빼내는 방법을 보여준다.
(int)(pi * 1000)/1000f;에서 (pi*1000)이 먼저 수행이 된다. pi는 float, 1000은 정수형이기 때문에 연산 결과는 float형인 3.141.592f가 됩니다.
(int)(3141.592f)/1000f에서 단항연산자인 캐스트연산자의 형변환이 수행됩니다. 3141.592f를 int로 변환하면 3141을 얻게 됩니다. 소수점 이하의 자리는 반올림 없이 버려집니다.
3141/1000f;에서 int와 float의 연산이므로 int가 float로 변환된 다음, float/float의 연산이 수행됩니다. float와 float의 연산이므로, float인 3.141f가 출력됩니다.






댓글 없음:

댓글 쓰기