본문 바로가기

Language/Java

자바에서 정확한 실수의 표현과 부동 소수점

반응형

자바를 공부하다가 정말 이상한 점을 발견했다. 아래의 코드의 출력 결과는 굉장히 충격적이었다. 

System.out.println(2.0 - 1.1);

나는 자신있게 '0.9' 를 외쳤지만 내 기대와 다른 결과가 도출 됬다.

0.899999999999999

뭐지... 내가 배웠던 수학이 잘못 되었는지에 대해 회의감이 발생하지만 알고나면 내 수학공부 과정이 틀리지 않았음을 알 수 있었다. 

 

오늘의 주제는 자바에서 2.0 - 1.1 의 계산 결과가 0.9가 나오지 않았던 이유에 대해 정리해보려고 합니다. 

 


 

  • 컴퓨터의 언어 (기계어)

컴퓨터는 내부적으로 0 과 1의 스트림만 이해 할 수 있습니다. 즉, 내가 컴퓨터 한테 '1+1 계산해줘'라고 해봤자 못알아 먹는다는 겁니다. '1+1 계산해줘' 를 2진수 체계로 변환해야만 컴퓨터가 이해하고 계산을 할 수 있습니다. 

 

그럼 컴퓨터는 내부적으로 0과 1만 이해할 수 있으니 아라비아 수 체계, 즉, 10진법의 수체계를 2진법으로 변환해야 합니다. 더 나아가 소수라면 어떨까요?

 

소수 또한 컴퓨터의 언어인 0 과 1로 변환을 해야합니다. 아까 위의 상황은 소수를 컴퓨터 언어로 변환하는 과정에서 생기는 문제라고 말할 수 있습니다. 

 


 

  • 부동 소수점과 IEEE

10진법으로 2진법으로 변환하는 방법에 대해 다루는 방법은 아래의 링크를 참고 해 주세요

 

 

컴퓨터에서의 실수 표현: 고정소수점 vs 부동소수점

어제 트위터 타임라인을 보다가, '부동소수점' 이라는 단어를 보면 어떻게 해석하게 되느냐는 그런 트윗이 RT로 넘어왔다. 생각해보니까 굉장히 비직관적인 단어다. 원래 영어단어는 floating point

gsmesie692.tistory.com

 

예를 들어 263를 16비트 2진수로 표현 하면 0000 0001 0000 0111 입니다. 

그럼 0.3을 2진수로 표현하면 어떻게 될까요?? 

 

0.0100 1100 1100 11 .... 이렇게 끝없이 이어지는 형태를 볼 수 있습니다. 

 

컴퓨터 내부에서는 어쩔 수 없이 표현할 수 있는 가장 근사치의 값이 저장됩니다. 

 

이 근사 값을 저장하는 방법에는 두가지가 있습니다. 

 

 

1. 고정 소수점 

 

정수를 표현하는 비트 수와 소수를 표현하는 비트 수를 미리 정해 놓고 해당 비트 만큼만 사용해서 수를 표현하는 방식

 

예를 들어 실수표현에 32비트를 사용하고 그중 부호 1비트 표시, 정수 16비트, 소수 15비트를 사용하도록 약속해 놓은 시스템이 있다고 가정했을때 

 

263.3을 표현하는 방식은 (0) 0000000100000111.010011001100110 과 같습니다. 

 

정수를 표현하는 bit를 늘리면 큰 수를 표현할 수 있지만 정밀한 수를 표현하기 힘들기 때문에 부동 소수점 방식을 사용합니다. 

 

2. 부동 소수점

 

부동 소수점을 표현하는 방식은 일반적으로 IEEE 에서 표준으로 제안한 방식을 사용합니다. 

 

IEEE (Institute of Electrical and Electronics Engineers) 사에서 개발한 컴퓨터에서 부동소수점을 표현하는 방식으로 가장 널리 사용되고 있습니다. 

 

이 방식에 따라 263.3 을 변환했을때 0 10000111 000000111010011001100110 으로 변환됩니다. 

 

하지만 여기서도 정확히 0.3을 나타낼 수 는 없습니다. 컴퓨터가 저장 할수 있는 비트수는 한정되어있고 0.3은 2진법으로 나누어 떨어 지지 않기 때문입니다. 

 

 

다시 돌아와 아래의 코드의 결과가 정확히 0.9가 나오지 않은 이유에 대해서 조금은 이해가 될것 같습니다. 

 

System.out.println(2.0 - 1.1);

 

컴퓨터의 2진법 체계를 사용하려고 10진수를 2진수로 변환하는 과정에서 정확한 값으로 변환하지 못했기 때문입니다. 

 

 

그럼 자바에서는 정확한 수를 계산 하기 위해서 어떻게 해결할 까요??

 


 

  • BigDecimal

자바에서는 이러한 부동 소수점 표현 방식의 오차를 해결하기 위해 BigDecimal 클래스를 제공하고 있습니다. 

 

가장 많이 사용하는 함수들을 정리 해보겠습니다. 

 

// 선언은 소수를 같이 표현하고자 한다면 String을 대입하고 정수를 표현할때는 정수를 대입해도 상관없다.
// Double 형으로 대입하면 소수표현이 안된다.

BigDecimal value = new BigDecimal("263.34");

// 덧셈
// ONE, TEN, ZERO 새가지 기본 옵션이 있고 BigDecimal 끼리 사칙연산이 가능하다.
// 264.34
value.add(BigDecimal.ONE);
-----------------------------------------------------------------------

// 뺄셈
263.34
value.subtract(BigDecimal.ONE);
-----------------------------------------------------------------------

// 곱셈
// 2633.4
value.multiply(BigDecimal.TEN);
-----------------------------------------------------------------------

// 나눗셈
// 나눗셈 메서드를 사용하면 정확하게 나누어 몫이 떨어 지지 않는 수의 경우 ArithmeticException 예외를 던진다. 
BigDecimal value1 = new BigDecimal("11");
BigDecimal value2 = BigDecimal.valueOf(3);

// Exception in thread "main" java.lang.ArithmeticException:
// Non-terminating decimal expansion; no exact representable decimal result.
value1.divide(value2);

// 때문에 몇번째 자리에서 올림, 내림, 반올림 할 것인지에 대해 정해 줘야 한다.
// 3.67
value1.divide(value2, 2, BigDecimal.ROUND_HALF_UP);

 

더 자세한 내용은  아래의 링크를 참고하면 될 것 같습니다. 

 

BigDecimal (Java SE 15 & JDK 15)

All Implemented Interfaces: Serializable, Comparable public class BigDecimal extends Number implements Comparable Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit int

docs.oracle.com

 

정리하자면 맨 처음 계산 하려고 했던 2.0 - 1.1 의 계산 결과에 오류가 있었던 이유는 컴퓨터의 수체계의 변환 방식에서 정밀한 값을 구할 수 없기 때문이고 자바에서는 이것을 최대한 해결하기 위해 BigDecimal 클래스를 제공하고 있습니다

반응형