본문 바로가기
Java/Java

Java의 소수점 계산 오류 및 해결

by oneny 2023. 7. 16.

소수점 계산

public class Calculate {

  public static void main(String[] args) {
    System.out.println(0.1 + 0.2); // 0.30000000000000004
  }
}
console.log(0.1 + 0.2); // 0.30000000000000004

프로그래밍에서 소수점 계산은 흔한 일이다. 우리 실생활에서 소수점을 계산해야 하는 경우가 많고, 달러로 계산할 때에도 소수점 계산은 흔히 사용된다. 그 때 만약 0.1 + 0.2와 같은 소수점 계산에서 0.30000000000000004로 결과가 나와 계산이 틀리게 되면 큰 문제가 발생할 수도 있다. 왜 0.1 + 0.2 계산에서 0.3이 아닌 0.30000000000000004이 나왔을까?

0.1과 0.2은 사실 각각 0.1, 0.2가 아니기 때문이다. 컴퓨터는 이진법을 사용해서 숫자를 비롯한 모든 데이터를 표현하고 처리한다. 하지만 이 방식으로는 모든 숫자를 정확하게 표현하지 못한다.

 

0.1의 이진수 표기
  => 0.0001100110011 ...
  => 1/2^4 + 1/2^5 + 1/2^8 + 1/2^9 + 1/2^12 + 1/2^13 + ...
  => 0.0999755859375

정수 부분은 십진법에서 2진법으로 바꿀 때 어떤 숫자든 표현이 가능하지만, 위 예시처럼 소수 부분은 0.1에 가까워질 뿐, 맞아떨이지지 않는다는 것을 확인할 수 있다. 2진법에서는 1/10이 무한소수가 된다는 말이다. 이런 이유도 우리가 코드에서 사용하는 자료형의 숫자는 2진법 기반이기 때문에 1/10을 정확히 표현하지 못하는 것이다. 특정 자릿수에서 반올림을 하는 즉시 0.1보다 큰 값이 돼버리고, 이런 값들이 더해지기 때문에 실제와는 다른 결과가 나오게 된다.

 

부동소수점

9.625의 부동소수점 변환
  => 1001.101
  => 1.001101
  => 0 1000001 00011010 00000000 00000000
           130-127 = 3(소수점 3칸 이동)

부동소수점은 모든 이진수 숫자를 위처럼 1.xxx..의 형식으로 변환한다. 이처럼 소수점이 떠서 움직인다고 해서 부동(Floating) 소수점이라고 표현한다. 부동소수점은 정수 자료형처럼 32비트 중 첫 비트는 양수와 음수를 구분하는데 사용하고, 그 다음에 오는 8비트로, 127과의 차이를 통해 소수점이 몇 칸을 움직일지를 나타낸다. 그리고 나머지 23자리에 소수점이 움직인 결과에서 소수점 뒤로 오는 부분들을 채워넣는다. 이렇게 하면 23비트를 숫자표현에 사용할 수 있고, 소수점의 자리도 필요한대로 움직일 수 있기 때문에 고정소수점보다 효율적으로 비트들을 활용해서 더더욱 다양한 숫자들을 표현해낼 수 있다. 이처럼 32비트 중 1비트, 8비트, 23비트를 각각 부호와 지수, 가수에 할당해서 사용하는 방식을 컴퓨터에서 널리 사용되는 IEEE 754 표준이다.

 

소수점 계산 해결(BigDecimal)

public class Calculate {

  public static void main(String[] args) {
    BigDecimal num1 = new BigDecimal("0.1");
    BigDecimal num2 = new BigDecimal("0.2");

    System.out.println(num1.add(num2)); // 0.3
    System.out.println(num1.subtract(num2)); // -0.1
    System.out.println(num1.multiply(num2)); // 0.02
    System.out.println(num1.divide(num2)); // 0.5
    System.out.println(num1.remainder(num2)); // 0.1
    System.out.println(num1.compareTo(num2)); // -1
    System.out.println(num2.compareTo(num1)); // 1
  }
}

자바에서는 BigDecimal이라는 클래스를 활용하여 이진수의 근사치를 저장하는 기본 자료형의 한계를 소프트웨어적으로 보완하여 정확한 실수 계산을 할 수 있다.

 

 

 

출처

부동소수점(+ 실수계산 오차가 생기는 이유)