카테고리 없음

왜 0.1 + 0.2는 0.3이 아닐까?

integerJI 2025. 12. 13. 17:23

최근에 유튜브를 보다 재밌는 걸 발견했다.

 

https://www.youtube.com/watch?v=-GsrYvZoAdA&list=LL&t=6s

 

코딩애플님의 영상이다.

 

영상에서는 자바스크립트에서 1.1 + 0.2를 계산했는데, 결과가 정확히 1.3이 아니라는 이야기가 나온다.

 

한번 알아보자

 

코드 짜보기

public class Main {

    public static void main(String[] args) {

        double a = 0.1;
        double b = 0.2;
        double sum = a + b;
        double target = 0.3;

        System.out.printf("0.1 + 0.2 계산값 : %.20f%n", sum);
        System.out.printf("      0.3 리터럴 : %.20f%n", target);

        System.out.println(sum == target);
        System.out.println(sum > target);
    }
}
0.1 + 0.2 계산값 : 0.30000000000000004000
      0.3 리터럴 : 0.30000000000000000000
false
true

 

프린트로 소수점 20자리까지 내려가서 보면,

두 값이 이미 다르다는 걸 확인할 수 있다.

 

0.1 + 0.2의 결과가 0.3보다 아주 조금 더 큰 값으로 

 

그래서 == 비교는 실패하고,

sum > targettrue가 된다.

 

정말 내부 값이 다른가?

 

앞에서는 소수점 아래를 길게 출력해서 두 값이 다르다는 건 확인했다.

 

저장된 값 자체가 다른 건가?

 

Double.toHexString()을 사용하면

double 값을 IEEE 754 기준의

16진수 표현으로 볼 수 있다.

 

10진수 말고 16진수로 봐보자

 

System.out.println("0.1 + 0.2 (hex) : " + Double.toHexString(sum));
System.out.println("0.3       (hex) : " + Double.toHexString(target));
0.1 + 0.2 (hex) : 0x1.3333333333334p-2
0.3       (hex) : 0x1.3333333333333p-2

 

두 값은 아예 다른 값으로 저장되어 있었다.

  • 0.1 + 0.2...33334
  • 0.3...33333

즉, 이 문제는 출력 방식의 문제가 아니라,

처음부터 메모리에 저장된 값이 달랐던 것이다.

 

그래서 == 비교가 실패하는 건 당연한 결과였다.

 

그러면?

 

금액, 포인트, 정산처럼

조금의 오차도 허용되지 않는 영역에서는

double 비교 자체가 위험하다.

 

이럴 때는 BigDecimal을 사용하면 된다.

BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal bdSum = bd1.add(bd2);
BigDecimal bdTarget = new BigDecimal("0.3");

System.out.println("BigDecimal 계산값: " + bdSum);
System.out.println("결과가 0.3과 같니? -> " + bdSum.equals(bdTarget));
BigDecimal 계산값: 0.3
결과가 0.3과 같니? -> true

 

 

BigDecimal은 부동소수점 방식이 아니라, 10진수를 그대로 표현한다.

 

즉, 처음부터 근사값이 아니라,

의도한 값 그대로 저장하고 계산하기 때문에 이런 문제 자체가 발생하지 않는다.

 

하지만 BigDecimal 쓸떄 주의해야할점이 있다.

정확한 계산이 필요할 때는 BigDecimal이 좋은 선택이지만

 

아래처럼 쓰면 안된다.

 

BigDecimal wrong = new BigDecimal(0.1);
BigDecimal correct = new BigDecimal("0.1");

System.out.println("new BigDecimal(0.1)    : " + wrong);
System.out.println("new BigDecimal(\"0.1\")  : " + correct);
System.out.println("둘이 같니? -> " + wrong.equals(correct));
new BigDecimal(0.1)    : 0.1000000000000000055511151231257827021181583404541015625
new BigDecimal("0.1")  : 0.1
둘이 같니? -> false

 

같은 0.1처럼 보이지만, 두 값은 전혀 다른 값이다.

 

new BigDecimal(0.1)0.1이라는 십진수를 BigDecimal로 바꾸는 게 아니라,

이미 오차가 포함된 double 0.1그대로 BigDecimal로 감싼 것이다.

 

반면,

 

new BigDecimal("0.1")사람이 의도한 십진수 0.1을 처음부터 정확하게 BigDecimal로 만든다.

 

BigDecimal은 만능 해결책이 아니다.

입력부터 정확해야 의미가 있다.

  • X new BigDecimal(double)
  • O new BigDecimal(String)
  • O BigDecimal.valueOf(double)

 

0.1 + 0.2 != 0.3 같은 부동소수점의 계산과 비교는 

컴퓨터가 숫자를 표현하는 방식에서 비롯된, 아주 자연스러운 결과다.

 

다들 알맞은 방식을 쓰고 결과를 도출해내길