Back-end/JAVA

[JAVA] 부동소수점에 대해 - 0.1 + 0.2 = 0.300...4?

화누파더 2024. 7. 12. 19:25
반응형

자바에서 실수가 있는 경우를 더하다 보면 황당한 일이 발생하기도한다. 분명히 0.1 + 0.2 = 0.3 인데

double a = 0.1;
double b = 0.2;
System.out.println(a+b);
// 결과 : 0.30000000000000004

 

왜 이런 현상이 일어나는지 알아보도록 하겠다. 물론 이러한 현상은 자바만의 문제는 아니다.

// 파이썬
a = 0.1 + 0.2;
print(a)
//결과 : 0.30000000000000004

 

 

 부동 소수점에 대해 알아보자

이번 포스팅에서는 위의 결과값이 왜 저렇게 나왔는지 설명하기 위해 아래의 개념들에 대해 알아보면서 설명을 진행하겠다.

  • 1byte
  • 고정 소수점 vs 부동소수점
  • 자바의 숫자 자료형 - float, double
  • 0.1 + 0.2를 확인해보자
  • 어떻게 비교 연산을 해야할까?

 

 

1byte

위의 결과가 어떻게 나오는지 알기 위해서 우선 필요한 지식은 1바이트의 의미이다.

1바이트는 8개의 비트로 이루어진 컴퓨터의 최소 처리 단위이다. 여기서 비트는 0과 1로만 저장이 가능하다. 

 

 

 

고정 소수점 vs 부동 소수점

 

 고정 소수점

고정 소수점은 정수부와 실수부가 들어갈 수 있는 부분이 정해져있어 나누어 처리하는 방식을 말한다.

 

 

부동 소수점

부동 소수점은 수에 $2^n$을 곱해 정수로 변환해 2진수로 변환하는 방식을 말한다. 이방식의 한계로 인해 정확한 값이 아닌 근사값이 발생하게 되고 맨 처음 말했던 0.300000...4 같은 에러가 발생하게 된다. 그럼에도 불구하고 적은 비트로 많은 수를 표현할 수 있기에 많은 언어에서 해당 방식을 사용한다.

 

 

장단점

고정 소수점

  • 장점 : 범위내의 숫자를 정확히 알 수 있다.
  • 장점 : 부동 소수점에 비해 연산이 빠르다.
  • 단점 : 정수부와 실수부를 어떻게 나누느냐에 따라 같은 비트수에서도 표현의 범위가 달라질 수 있다.

부동 소수점

  • 장점 : 작은 비트수로 넓은 범위의 숫자 표현이 가능하다.
  • 장점 : IEEE 754 표준으로 다양한 플랫폼에서 사용이 가능하다.
  • 단점 : 고정 소수점에 비해 연산이 느리다.
  • 단점 : 소수점 표현의 정밀도가 떨어져 근사값이 저장될 수 있다.

 

 

float, double

float과 double는 자바의 실수형을 나타내는 자료형이다.

타입 크기 접미사
float 4바이트
(부호 1비트, 지수부 8비트, 가수부 23비트)
f 또는 F
double 8바이트
(부호 1비트, 지수부 11비트, 가수부 52비트)
d 또는 D (생략 가능)

부호 - 음수인지 양수인지를 나타냄

지수부 -  1.xxxx $\times 2^n$ 으로 표현할때 127+n 이다. 여기서 127은 지수의 음수 양수 구분을 위한 편향값이다.

가수부 -   1.xxxx $\times 2^n$에서 xxxx에 해당하는 부분이다.

// double 타입 실수 생성
double pi = 3.141592;
double pid = 3.141592D;
// float 타입 실수 생성
float pif = 3.14f;
// D가 생략 가능한지 확인
System.out.println(pi == pid);  //true

 

 

0.1  +  0.2를 확인해보자

표현될 숫자 부호 지수부 가수부 2진수로 표현(52자리까지 남기고 제거)
0.1 0(양수) 01111111011 1001100110011001100110011001100110011001100110011001 0.00011001100110011001100110011001100110011001100110011001
0.2 0(양수) 01111111100 1001100110011001100110011001100110011001100110011001 0.0011001100110011001100110011001100110011001100110011001
+ 0(양수) 01111111101 0011001100110011001100110011001100110011001100110011 0.010011001100110011001100110011001100110011001100110011

이와 같이 0.1 + 0.2는 우리가 흔히 생각하는 10진법에 기반한 계산이 아니라 2진법으로 변환된 후 이루어지는 계산이기 때문에 변환과 계산 과정에서 오차가 발생하는 것이다.

 

 

어떻게 비교연산을 해야할까?

그렇다면 이러만 문제들은 어떻게 비교연산을 해야할까?

 

오차를 허용한다.

오차 범위를 지정하여 해당 오차 범위 이내이면 동일하다고 판단하는 것이다.

double a = 0.1;
double b = 0.2;
double c = a + b;

double epsilon = 1e-10; // 허용 오차 범위
if (Math.abs(c - 0.3) < epsilon) {
    System.out.println("a + b는 0.3입니다.");
} else {
    System.out.println("a + b는 0.3이 아닙니다.");
}

 

BigDecimal

BigDecimal 클래스를 이용하여 부동소수점의 연산을 정확히할 수도 있다. BigDecimal은 scale 조정이 가능하며 보다 정밀한 수치를 얻을 수 있으나 연산속도가 느려 정밀한 연산이 필요한 작업이 아니면 사용하지 않습니다.

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal c = a.add(b);

System.out.println("0.1 + 0.2 = " + c); 
// 결과: 0.3

 

 

반응형