학습 자료

연산자와 형변환 — 5 나누기 2가 2인 이유


Article

계산을 시켜보자. 5를 2로 나눈다.

System.out.println(5 / 2);
콘솔
  • 2

2.5 가 아니다. 소수점이 잘려나갔다.

버그가 아니다. 자바는 지금 시킨 대로 했다. 왜 이게 시킨 대로인지 이해하면, 형변환이라는 이 강의의 주제가 저절로 풀린다.

정수끼리 나누면 정수가 나온다

5int2int 다. 자바는 intint 를 계산하면 결과도 int 여야 한다고 본다.

2.5int 가 아니다. 그러니 소수점 아래를 버린다.

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 / 0NaN 인데, Not a Number 의 준말이다. "숫자가 아닌 무언가"라는 뜻이다.

반면 정수를 0으로 나누면 프로그램이 그 자리에서 죽는다. ArithmeticException 이다. 18강에서 이 죽음을 붙잡는 법을 배운다.

자동으로 바뀌는 방향은 하나뿐이다

intdouble 상자에 넣어보자.

double d = 10;      // 잘 된다

10int 인데 double 상자에 들어갔다. 자바가 몰래 10.0 으로 바꿔 넣었다.

반대로 해보자.

int i = 3.9;
터미널
  • error: incompatible types: possible lossy conversion from double to int

거절한다. "손실이 생길 수 있는 변환" 이라는 것이다. 3.9int 에 넣으면 .9 가 사라진다.

정리하면 자바가 알아서 바꿔주는 방향은 작은 그릇에서 큰 그릇으로 한 방향뿐이다.

자동으로 넓어지는 방향 (손해가 없다)
  1. byte
  2. short
  3. int
  4. long
  5. float
  6. double여기가 가장 크다

charint 로 넓어진다. 다만 char 는 이 줄에 딱 끼워 넣기 애매해서 따로 뒀다.

반대 방향으로 가려면 직접 책임지겠다고 선언해야 한다. 그게 캐스팅이다.

캐스팅 — 손해를 감수하겠다는 서명

괄호 안에 타입을 적는다.

int i = (int) 3.9;
System.out.println(i);
콘솔
  • 3

3.93 이 됐다. 반올림이 아니라 버림이다.

캐스팅은 "알고 있으니 잘라내라"는 서명이다. 컴파일러는 서명을 보면 더 묻지 않는다. 그래서 캐스팅은 위험하다. 잘못 서명하면 이런 일이 생긴다.

오버플로 — 그릇을 넘치면 반대편으로 나온다

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/30.3333… 이라고밖에 못 쓰는 것과 같다.

컴퓨터에서 소수를 다루는 거의 모든 언어가 똑같이 동작한다. 하드웨어가 그렇게 만들어졌기 때문이다.

그래서 소수끼리 == 로 비교하면 안 된다. 돈 계산에는 double 을 아예 쓰지 않는다. 자바에는 BigDecimal 이라는 정확한 도구가 따로 있다.

나머지 연산자들

앞으로 계속 쓸 것들만 모았다.

종류연산자설명
산술+ - * / %/ 는 정수끼리면 몫만 남는다
복합 대입+= -= *= /=a += 1a = a + 1 과 거의 같다
증감++ --1씩 늘리거나 줄인다
비교> < >= <=결과는 boolean 이다
같음== !=참조형에는 함부로 쓰지 않는다 — 6강
논리&& || !그리고 · 또는 · 아니다

비교와 논리 연산자의 결과는 언제나 boolean 이다. 7강 조건문에서 이 값을 쓴다.

&&|| 는 게으르다. a && b 에서 a 가 이미 falseb 는 쳐다보지도 않는다. 결과가 뻔하기 때문이다. 이 성질은 나중에 null 을 피해 갈 때 요긴하게 쓰인다.

복합 대입에는 캐스팅이 숨어 있다

표에서 a += 1a = 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 가 아니라 int66 이 나온다. charint 로 넓어져서 계산되기 때문이다. 그 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 에서 어떤 일을 벌이는지가 다음 강의의 주제다.

정리하면

자바는 손해 보지 않는 방향으로만 알아서 바꾸고, 나머지는 캐스팅으로 서명하게 한다.

다시 짚어보기
  1. 01

    정수끼리 나누면 정수다

    5 / 2 는 2 다. 소수가 필요하면 한쪽을 double 로 만든다.

    System.out.println((double) 5 / 2);  // 2.5
  2. 02

    넓어지는 쪽은 자동, 좁아지는 쪽은 캐스팅

    int → double 은 그냥 되고, double → int 는 (int) 를 적어야 한다.

  3. 03

    캐스팅은 반올림이 아니다

    (int) 3.9 는 3, (int) -3.9 는 -3. 0 쪽으로 버린다.

  4. 04

    그릇을 넘치면 조용히 반대편으로 돈다

    에러도 경고도 없다. 커질 값은 long 으로 계산 전에 넓힌다.

    long total = (long) Integer.MAX_VALUE + 1;
  5. 05

    소수는 == 로 비교하지 않는다

    0.1 + 0.2 는 0.3 이 아니다. 돈 계산에는 BigDecimal 을 쓴다.

다음 강의는 String 이다. 4강에서 참조형이라고만 하고 넘어갔던 그 타입이다.

거기서 자바 입문자가 가장 많이 틀리는 문제를 만난다. 글자가 똑같은 두 문자열을 == 로 비교하면 무엇이 나올까? 답이 하나가 아니라는 게 문제다.