{ }codelper

배우기 › Java

더 나아가기 · 20

정수와 실수

0.1 + 0.2가 0.3이 아닌 이유, 소수 자리 맞추기, 반올림 규칙, 정수 넘침까지.

수에는 정수와 실수 두 종류가 있어요.
정수는 정확하지만 한계가 있고, 실수는 소수를 담지만 아주 작은 오차가 생겨요.
아래 예제는 나눗셈을 소수 둘째 자리까지 찍고, 버림·내림·반올림과 큰 수 더하기를 차례로 보여 줘요.

import java.util.Scanner; public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int a = in.nextInt(), b = in.nextInt();        double x = Double.parseDouble(in.next());        double h = Double.parseDouble(in.next());        int big = in.nextInt();         System.out.println(String.format("%.2f", (double) a / b)); // a / b만 쓰면 0        double s = 0.1 + 0.2; // 0.30000000000000004        System.out.println(Math.abs(s - 0.3) < 1e-9 ? "close" : "far");        System.out.println((int) x + " " + (int) Math.floor(x)); // -3 -4        System.out.println(Math.round(h)); // 2.5 → 3 (long)        System.out.println((long) big + 1); // big + 1은 int라 -2147483648    }}
stdin
2 3
-3.7
2.5
2147483647
stdout
0.67
close
-3 -4
3
2147483648

핵심

  • 정수(int, long)는 딱 떨어지는 수를 정확히 담아요.
    실수(double)는 이진수로 적기 때문에 아주 작은 오차가 생겨요.
  • 0.1 + 0.2는 어느 언어에서나 0.30000000000000004예요.
    모든 언어의 double이 같은 IEEE 754 배정밀도라서 오차도 똑같아요.
  • '딱 반'(2.5)을 반올림하는 방향은 언어마다 달라요.
    짝수 쪽(은행원 반올림), +∞ 쪽, 0에서 먼 쪽 세 가지가 있어요.
  • 실수를 정수로 바꾸면 소수 부분이 0 쪽으로 버려져요.
    그래서 -3.7을 바꾸면 -3이고, 작은 쪽으로 내리는 floor(-3.7)은 -4예요.
  • 정수에도 한계가 있어요.
    32비트 int는 2147483647(약 21억)까지라서, 그보다 큰 수는 64비트 정수에 담아요.

언어별로 쓰는 법

Pythonf"{x:.2f}" — round(2.5)는 2(짝수 쪽).
int(x)는 버림.
정수는 크기 제한이 없어서 넘치지 않아요
JavaScriptx.toFixed(2) — Math.round는 +∞ 쪽(2.5 → 3, -2.5 → -2), 버림은 Math.trunc(x).
수가 하나(실수)라 2^53(Number.MAX_SAFE_INTEGER)까지만 정수가 정확하고, 더 크면 BigInt(123n)
TypeScriptJavaScript와 같아요
JavaString.format("%.2f", x) — Math.round는 +∞ 쪽(-2.5 → -2).
int가 넘치면 오류 없이 -2147483648로 돌아가요.
큰 수는 long
C#x.ToString("F2") — Math.Round(2.5)는 2(짝수 쪽), Math.Round(x, MidpointRounding.AwayFromZero)로 바꿀 수 있어요.
int가 넘치면 돌아가고, checked 안에서는 OverflowException
C++std::fixed << std::setprecision(2) — 한 번 쓰면 그 뒤의 실수 출력에도 계속 적용돼요.
round는 0에서 먼 쪽(-2.5 → -3).
부호 있는 정수 넘침은 정의되지 않은 동작이라 큰 수는 long long
Cprintf("%.2f", x) — 반올림·넘침은 C++와 같아요.
long은 Windows에서 32비트라 long long이 안전해요
Craykon글자.소수자리맞추기(x, 2) — 수학.반올림은 0에서 먼 쪽(-2.5 → -3).
버림은 수학.몫(x, 1), 내림은 수학.내림(x).
수가 하나(실수)라 2^53까지만 정수가 정확하고 BigInt는 없어요

자주 하는 실수

정수끼리 나눴더니 소수가 사라졌어요Java · C# · C · C++

int a = 7, b = 2;
double r = a / b;

→ r은 3.0이에요. 정수 나눗셈을 먼저 하고 나서 실수로 바꿔요

한쪽을 먼저 실수로 바꿔요: (double)a / b.

int가 넘쳤어요Java · C# · C · C++

int n = 2147483647;
n = n + 1;

→ Java·C#은 오류 없이 -2147483648. C·C++은 정의되지 않은 동작이에요

큰 수는 long(C·C++은 long long)에 담아요.

크레이콘 출력이 0.3으로 보여요Craykon

콘솔.쓰기(0.1 + 0.2);
콘솔.쓰기(0.1 + 0.2 == 0.3);

→ 0.3과 false — 소수 아홉째 자리에서 반올림해 보여 줄 뿐, 값은 0.30000000000000004예요

보이는 값을 믿지 말고, 차이가 아주 작은지로 비교해요.

언어마다 다른 점
실수끼리 ==로 비교하지 마세요.
차이의 절댓값이 아주 작은지(abs(a - b) < 1e-9) 보고, 돈처럼 딱 떨어져야 하는 값은 100을 곱해 정수(원·센트)로 다뤄요.

연습 문제