Java

[Java] 연산자와 형변환

seonggu 2023. 4. 28. 16:26

 

연산자 부분에서 산술 연산자 파트를 학습하면서 중요하다고 생각한 것이 많아 포스팅으로 정리하려고 한다.

 

3.1 사칙 연산자 + - * /

  • 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/)을 수행하는 연산자들
  • 곱셈(*), 나눗셈(/), 나머지(%) 연산자가 덧셈(+), 뺄셈(-) 연산자보다 우선순위가 높다. 나머지도 포함됨
  • 피연산자가 정수형인 경우, 나누는 수로 0을 사용불가 0으로 나누면 에러발생

 

사칙 연산자를 베이스로 형변환과 더불어 단항 연산자를 포함한 연산들이 발생했을 때 어떤 순서로 처리할 것인지에 대해 예시와 함께 정리해보자.

 

⭐올바른 나눗셈

int a = 10;
int b = 4;

System.out.printf("%d / %d = %d\\n" a, b, a/b); // 2 
System.out.printf("%d / %f = %f\\n" a, (float)b, a/(float)b); // 2.500000

연산결과 타입에서 정리했듯.

두 피연산자가 모두 int타입인 경우, 연산결과 역시 int타입이어서 연산결과가 2.5일지라도

int타입의 값인 2를 결과로 얻음. 소수점 이하는 버려지기 때문 반올림이 발생하지 않는다는 것을 주의하자

 

✅ 올바른 연산 결과를 얻기 위해서는 2번째 출력문처럼 어느 한쪽을 실수형으로 형변환 해야 함.

이때 다른 한쪽은 실수형으로 자동 형변환됨.

 

 

⭐정수형 0과 실수형 0.0의 나눗셈

System.out.println(3/0);
System.out.println(3/0.0); 

출력문 1번처럼 피연산자가 정수형인 경우 0으로 나누면 에러(ArithmeticException)발생

출력문 2번처럼 부동 소수점 값인 0.0f, 0.0d로 나누는 것은 가능하지만 infinity(무한대)가 됨.

 

 

⭐사칙 연산 후 연산결과에 대한 형확인하기.

byte a = 10;
byte b = 20;
byte c = a + b; //error (byte)(a+b);
  • a와 b는 모두 int형보다 작은 byte형이다. 하여 연산자 ‘+’는 이 두 개의 피연산자들을 int형으로 변환한 다음 덧셈을 수행한다.
  • ‘a+b’의 계산결과는 int형(4 byte)이므로 byte형 c에 저장할 수 없음. 하여 에러가 발생했고, byte형변환을 해줘야 변수 c에 저장이 가능하다.

 

 

⭐ 연산 전에 형변환하기

int a = 1_000_000; //가독성을 위해 언더바 사용가능
int b = 2_000_000; 

long c = a * b; // -1454759936
  • long타입(8byte)이기 때문에 $2 * 10^{12}$을 저장하기에 충분하므로 ‘2,000,000,000,000’이 출력될 것 같지만, 결과는 오버플로우
  • int타입과 int타입의 연산결과는 int타입이다.
  • ‘a * b’의 결과가 이미 int타입의 값(-1454759936)이므로 long으로 자동 형변환이 되어도 값이 변화하지 않는다.
long c = (long)a * b; //a, b 둘 중하나는 long 타입으로 형변환해야함.

 

 

⭐ 연산순서에 따른 결과

int a = 1_000_000;

int result1 = a * a / a;         // -727
int result2 = a / a * a;         // 1,000,000
  • 1,000,000에 1,000,000 먼저 곱한 후에 나누는 것과 먼저 나눈 후에 곱하는 것의 연산결과가 달랐다. 이는 먼저 곱하는 경우 int의 넘어서기 때문에 예상했던 결과와 달랐다.
  • 이처럼 같은 의미의 식이라도 연산의 순서에 따라 다른 결과를 얻을 수 있다.

 

⭐ 피연산자는 문자도 가능 p.103

char a = 'a';
char d = 'd';
char zero = '0';
char two = '2';

System.out.printf("'%c' - '%c' = %d\\n", d, a, d - a);    //'d'-'a' =3
System.out.printf("'%c' - '%c' = %d\\n", two, zero, two - zero);   //'2' - '0' =2
  • 사칙연산의 피연산자로 숫자뿐만 아니라 문자도 가능하다. 문자는 실제로 해당 문자의 유니코드 (부호 없는 정수)로 바뀌어 저장되며 문자 간의 사칙연산은 정수 간의 연산과 동일하다.

📌 주요 유니코드(0~9, A~Z, a~z는 연속배치되어 있음.) 문자 ‘0’ : 48 문자 ‘A’ : 65 문자 ‘a’ : 97

 

 

⭐ 문자의 여러 연산결과

char c1 = 'a';
char c2 = c1;
char c3 = ' ';

1) int i = c1 + 1; // 'a'+1 = 98
2) c3 = (char)(c1 + 1);  // b
3) c2++;
	c2++; // c
  1. c1이 char형이므로 int형으로 형변환 후 덧셈연산 수행
  2. c1+1 덧셈연산 결과가 int이므로 결과를 char형 변수 c3에 담기 위해 형변환
  3. c2에 담긴 ‘a’ 97을 두 번 증가 99가 되어 c2 출력 시 ‘c’

 

⭐ 리터럴 연산

char c1 = 'a';

char c2 = c1 + 1;   //컴파일 에러 발생
char c3 = 'a' + 1;  //컴파일 에러 없음 'b'
  • 덧셈 연산자와 같은 이항 연산자는 int보다 작은 타입의 피연산자를 int로 자동 형변환한다고 배웠는데, 어떻게 (char)

☑️ (’a’ + 1); 코드처럼 형변환 하지 않고도 문제가 없을까?

‘a’+1이 리터럴 간의 연산이기 때문에 문제가 없다.

상수 또는 리터럴 간의 연산은 실행 과정 동안 변하는 값이 아니기 때문에, 컴파일 시 컴파일러가 계산해서 그 결과로 대체함으로써 코드를 보다 효율적으로 만든다.

 

📌 컴파일러가 미리 덧셈연산을 수행함.

 

올바르게 c2변수에 저장하려면 다음과 같이 형변환을 해주자.

char c2 = c1 + 1; → char c2 = (char) (c1 + 1);

 

 

⭐ 연산과 가독성

int sec = 60 * 60 * 24; //86400
  • 코드의 가독성과 유지보수를 위해서 위처럼 int 타입의 변수 sec에 하루(day)를 초(second) 단위로 변환하는 값을 저장하는 코드를 보면 ‘86400’이라는 값보다 ‘606024’와 같이 적어주는 것이 이해하기도 좋고 오류가 발생할 여지가 적다.
  • 예시로 반나절(12시간)을 값으로 변경해야 한다면 계산할 필요 없이 ‘606012’로 변경하면 좋다.

📌 중요한 것은 이렇게 풀어써도 컴파일러에 의해 미리 계산되기 때문에 실행 시 성능차이가 없다.

 

 

⭐ 많이 쓰이는 대소문자 변환

char lowerCase = 'a';
char upperCase = (char)(lowerCase - 32); //'A' 

  • 이는 유니코드 대문자 A가 소문자 a보다 코드값이 32가 적으므로 a의 코드값에 32를 빼면 되고, 대문자 A에 32를 더하면 소문자 a가 된다.
  • 많이 쓰임. 앞에서 배운 것처럼 ‘a’ - 32를 해주면 형변환을 하지 않아도 된다.

 

⭐ 소수점 n자리까지 빼내기

float pi = 3.141592f;
float shortPi = (int)(pi * 1000) / 1000f; // 3.141

앞서 짚고 넘어가기.

  • int형 간의 나눗셈을 수행하면 실수형이 아닌 int임
  • int형 간의 나눗셈을 수행하면 반올림이 아닌 내림

연산 수행 과정

  1. 괄호 안에 ‘pi * 1000’ 수행 pi는 이때 float → 3141.592f
  2. 단항 연산자인 형변환 연산자의 형변환 수행 (int) 이때, 소수점 이하 버림→ 3141
  3. int와 float의 연산이므로 int가 float 변환 3141.0f / 1000f → 3.141f

하여 float와 float의 나눗셈 결과 이므로 3.141f

 

 

⭐ 소수점을 버림이 아닌 반올림이 되게 해 보자.

double pi = 3.141592;
double shortPi = (int)(pi * 1000 + 0.5) / 1000.0; // 3.142
  • 이전 예제와 다른 점은 반올림을 위해 0.5를 더해 준다.

연산 수행 과정

  1. 괄호 안에 ‘pi * 1000’ 수행 pi는 이때 double → 3141.592 여기에 다음 연산인 0.5를 더하면 3142.092
  2. 형변환 (int) → 3142
  3. int와 double의 연산, int가 double로 변환 후 연산 → 3142.0 / 1000.0 ⇒ 3.142

 

⭐ Math.round() 메서드로 반올림하기 사용하기

double pi = 3.141592;
double shortPi = Math.round(pi * 1000) / 1000.0; // 3.142
  • round메서드는 매개변수로 받은 값을 소수점 첫째 자리에서 반올림을 하고
    그 결과를 정수로 돌려준다. Math.round(3142 / 1000.0)

 

참고자료 -