연산자와 형변환 — 5 나누기 2가 2인 이유
Article
계산을 시켜보자. 5를 2로 나눈다.
System.out.println(5 / 2);
- 2
2.5 가 아니다. 소수점이 잘려나갔다.
버그가 아니다. 자바는 지금 시킨 대로 했다. 왜 이게 시킨 대로인지 이해하면, 형변환이라는 이 강의의 주제가 저절로 풀린다.
정수끼리 나누면 정수가 나온다
5 도 int 고 2 도 int 다. 자바는 int 와 int 를 계산하면 결과도 int 여야 한다고 본다.
2.5 는 int 가 아니다. 그러니 소수점 아래를 버린다.
System.out.println(5 / 2); // 2
System.out.println(5.0 / 2); // 2.5
System.out.println(5 / 2.0); // 2.5
System.out.println((double) 5 / 2); // 2.5
- 2
- 2.5
- 2.5
- 2.5
한쪽만 double 이어도 결과가 double 이 된다. 양쪽 타입이 다르면 더 큰 쪽에 맞춰서 계산하기 때문이다.
버려진 나머지가 궁금하면 % 를 쓴다.
System.out.println(7 / 2); // 3 — 몫
System.out.println(7 % 2); // 1 — 나머지
% 는 짝수 판별에 자주 쓴다. n % 2 == 0 이면 짝수다. 8강 반복문에서 다시 만난다.
0으로 나누면
정수와 소수의 태도가 완전히 다르다.
System.out.println(1.0 / 0); // Infinity
System.out.println(0.0 / 0); // NaN
System.out.println(1 / 0); // 여기서 죽는다
- Infinity
- NaN
- Exception in thread "main" java.lang.ArithmeticException: / by zero
- at Main.main(Main.java:5)
소수는 태연히 Infinity 를 내놓는다. 0.0 / 0 은 NaN 인데, Not a Number 의 준말이다. "숫자가 아닌 무언가"라는 뜻이다.
반면 정수를 0으로 나누면 프로그램이 그 자리에서 죽는다. ArithmeticException 이다. 18강에서 이 죽음을 붙잡는 법을 배운다.
자동으로 바뀌는 방향은 하나뿐이다
int 를 double 상자에 넣어보자.
double d = 10; // 잘 된다
10 은 int 인데 double 상자에 들어갔다. 자바가 몰래 10.0 으로 바꿔 넣었다.
반대로 해보자.
int i = 3.9;
- error: incompatible types: possible lossy conversion from double to int
거절한다. "손실이 생길 수 있는 변환" 이라는 것이다. 3.9 를 int 에 넣으면 .9 가 사라진다.
정리하면 자바가 알아서 바꿔주는 방향은 작은 그릇에서 큰 그릇으로 한 방향뿐이다.
- byte
- short
- int
- long
- float
- double여기가 가장 크다
char 도 int 로 넓어진다. 다만 char 는 이 줄에 딱 끼워 넣기 애매해서 따로 뒀다.
반대 방향으로 가려면 직접 책임지겠다고 선언해야 한다. 그게 캐스팅이다.
캐스팅 — 손해를 감수하겠다는 서명
괄호 안에 타입을 적는다.
int i = (int) 3.9;
System.out.println(i);
- 3
3.9 가 3 이 됐다. 반올림이 아니라 버림이다.
캐스팅은 "알고 있으니 잘라내라"는 서명이다. 컴파일러는 서명을 보면 더 묻지 않는다. 그래서 캐스팅은 위험하다. 잘못 서명하면 이런 일이 생긴다.
오버플로 — 그릇을 넘치면 반대편으로 나온다
int 가 담을 수 있는 가장 큰 수에 1 을 더해보자.
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
- 2147483647
- -2147483648
가장 큰 수에 1을 더했더니 가장 작은 수가 됐다.
에러도 안 난다. 경고조차 없다. int 는 4바이트짜리 그릇이고, 그 그릇이 표현할 수 있는 수의 끝에서 한 칸 더 가면 반대편 끝으로 돌아온다. 자동차 계기판이 999999 다음에 000000 으로 돌아가는 것과 같다.
돈이나 조회수처럼 커질 수 있는 값은 long 을 쓴다. 21억이 생각보다 금방이다.
0.1 + 0.2
소수 계산에는 다른 함정이 있다.
System.out.println(0.1 + 0.2);
System.out.println(0.1 + 0.2 == 0.3);
- 0.30000000000000004
- false
자바가 이상한 게 아니다. double 은 소수를 2진법으로 저장하는데, 0.1 은 2진법으로 딱 떨어지지 않는다. 10진법으로 1/3 을 0.3333… 이라고밖에 못 쓰는 것과 같다.
컴퓨터에서 소수를 다루는 거의 모든 언어가 똑같이 동작한다. 하드웨어가 그렇게 만들어졌기 때문이다.
그래서 소수끼리 == 로 비교하면 안 된다. 돈 계산에는 double 을 아예 쓰지 않는다. 자바에는 BigDecimal 이라는 정확한 도구가 따로 있다.
나머지 연산자들
앞으로 계속 쓸 것들만 모았다.
| 종류 | 연산자 | 설명 |
|---|---|---|
| 산술 | + - * / % | / 는 정수끼리면 몫만 남는다 |
| 복합 대입 | += -= *= /= | a += 1 은 a = a + 1 과 거의 같다 |
| 증감 | ++ -- | 1씩 늘리거나 줄인다 |
| 비교 | > < >= <= | 결과는 boolean 이다 |
| 같음 | == != | 참조형에는 함부로 쓰지 않는다 — 6강 |
| 논리 | && || ! | 그리고 · 또는 · 아니다 |
비교와 논리 연산자의 결과는 언제나 boolean 이다. 7강 조건문에서 이 값을 쓴다.
&& 와 || 는 게으르다. a && b 에서 a 가 이미 false 면 b 는 쳐다보지도 않는다. 결과가 뻔하기 때문이다. 이 성질은 나중에 null 을 피해 갈 때 요긴하게 쓰인다.
복합 대입에는 캐스팅이 숨어 있다
표에서 a += 1 이 a = a + 1 과 "거의" 같다고 했다. 거의라고 쓴 이유가 있다.
char c = 'A';
c += 1; // 잘 된다
System.out.println(c); // B
char c = 'A';
c = c + 1; // 컴파일 에러
- error: incompatible types: possible lossy conversion from int to char
'A' + 1 을 계산하면 char 가 아니라 int 인 66 이 나온다. char 가 int 로 넓어져서 계산되기 때문이다. 그 66 을 다시 char 상자에 넣으려니 좁아지는 방향이라 거절당한다.
그런데 c += 1 은 된다. 복합 대입 연산자는 캐스팅을 몰래 넣어준다. c = (char)(c + 1) 로 바꿔주는 셈이다.
편하지만, 몰래 잘라낸다는 뜻이기도 하다. 이름 그대로 편의 문법이다.
문자열에 붙는 +
+ 는 숫자를 더하지만, 한쪽이 String 이면 이어붙인다.
System.out.println(1 + 2 + "x");
System.out.println("x" + 1 + 2);
- 3x
- x12
같은 재료인데 결과가 다르다. + 는 왼쪽부터 차례로 계산하기 때문이다.
첫 줄은 1 + 2 를 먼저 해서 3 을 만들고, 거기에 "x" 를 붙인다. 둘째 줄은 "x" + 1 이 먼저라 "x1" 이 되고, 거기에 2 를 또 붙인다.
이 + 가 String 에서 어떤 일을 벌이는지가 다음 강의의 주제다.
정리하면
자바는 손해 보지 않는 방향으로만 알아서 바꾸고, 나머지는 캐스팅으로 서명하게 한다.
- 01
정수끼리 나누면 정수다
5 / 2 는 2 다. 소수가 필요하면 한쪽을 double 로 만든다.
System.out.println((double) 5 / 2); // 2.5 - 02
넓어지는 쪽은 자동, 좁아지는 쪽은 캐스팅
int → double 은 그냥 되고, double → int 는 (int) 를 적어야 한다.
- 03
캐스팅은 반올림이 아니다
(int) 3.9 는 3, (int) -3.9 는 -3. 0 쪽으로 버린다.
- 04
그릇을 넘치면 조용히 반대편으로 돈다
에러도 경고도 없다. 커질 값은 long 으로 계산 전에 넓힌다.
long total = (long) Integer.MAX_VALUE + 1; - 05
소수는
==로 비교하지 않는다0.1 + 0.2 는 0.3 이 아니다. 돈 계산에는 BigDecimal 을 쓴다.
다음 강의는 String 이다. 4강에서 참조형이라고만 하고 넘어갔던 그 타입이다.
거기서 자바 입문자가 가장 많이 틀리는 문제를 만난다. 글자가 똑같은 두 문자열을 == 로 비교하면 무엇이 나올까? 답이 하나가 아니라는 게 문제다.