영어 공부겸 Medium의 개발 블로그를 탐방하던 도중에 이런 포스트를 찾았습니다.
그리고 최근 개발 커뮤니티의 cs 질문 게시판에서 비슷한 주제의 질문을 보게됐는데요.
" 이 코드는 왜 false가 나올까요? "
console.log(0.1 + 0.2 === 0.3);
먼저 떠오른 대답은 "컴퓨터는 실수를 정확하게 저장할 수 없다." 였습니다.
하지만 연쇄적으로 떠오른 "그럼 왜 정확히 저장할 수 없나요?"라는 질문에는 정확히 대답을 할 수 없었습니다.
그래서 이렇게 된 김에 제대로 정리해보려고 합니다.
사전지식. 컴퓨터가 소수점 아래를 저장하고 사용하는 방법
컴퓨터는 모든 데이터를 2진수, 즉 bit를 사용해 저장하고 사용합니다. 그리고 이건 실수를 다룰 때도 마찬가지입니다.
따라서 컴퓨터는 소수점 아래를 다음 그림처럼 2진수로 바꿔서 저장하고 사용합니다.
컴퓨터가 모든 수를 "정확하게" 표현할 수 없는 이유
정수(소수점 위 부분)의 경우
모든 정수는 이진수로 정확하게 변환 될 수 있습니다.
그 이유는 0과 양의 정수 중에 가작 작은 수인 1을 bit로 표현할 수 있기 때문입니다.
실수(소수점 아래 부분)의 경우
10진 소수 중 일부는 이진수로 정확히 변환되지 않는 무한 소수가 존재합니다. ( 만약 저장하고 싶다면 무한개의 자원(bit)가 필요합니다. )
다음 그림은 컴퓨터가 0.1을 저장하고 출력하는 과정입니다. ( 그림에서는 예시를 들고자 반올림을 중간에서 끊었기 때문에 컴퓨터가 0.1을 0.999755859375로 저장했지만, 실제로는 컴퓨터에서 IEEE 754 64비트 부동소수점(double) 형식으로 0.1을 저장하면 근사값의 형태로 저장되며 다음 단락에서 실제 출력으로 그 값을 확인할 수 있습니다. )
이를 통해, 컴퓨터에 0.1을 저장하려고 한다면 0.1에 가까운 값을 저장할 뿐 정확한 값을 저장할 수 없음을 알 수 있습니다.
위 내용을 통해 왜 false가 나오는지 요약하면 다음과 같습니다.
애당초 0.1과 0.2가 아닌 값들을 더했는데 어떻게 0.3이 되겠는가?!
( 사실 0.3도 0.3이 아니지만...)
0.1과 0.2의 실제 출력과 의문점
이제 실제 코드와 출력을 통해 저장된 0.1과 0.2의 값 확인해보겠습니다.
(참고로 아래 저장된 근사값은 double(64비트)에 저장된 값입니다.)
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
double value1 = 0.1;
double value2 = 0.2;
// BigDecimal로 double 값의 근사값 출력
BigDecimal approx1 = new BigDecimal(value1);
BigDecimal approx2 = new BigDecimal(value2);
System.out.println("0.1의 근사값 (double): " + approx1.toPlainString());
System.out.println("0.2의 근사값 (double): " + approx2.toPlainString());
}
}
하지만 이상합니다.
두 값을 더하면 대충 0.300000000000000016~이고,
이를 반올림하면 0.30000000000000002입니다.
근데 왜 합을 출력해보면 0.30000000000000004가 출력될까요?
그 이유는 '사전지식'에서 말했듯 컴퓨터는 모든 데이터를 2진수로 저장하고 사용하기 때문입니다.
즉 덧셈 또한 10진수가 아닌 2진수로 진행됩니다.
- 10진수 덧셈 : 0.100000000000000005~(0.1) + 0.200000000000000011~(0.2)
- 2진수 덧셈(실제 진행되는 덧셈) : 0.0001100110011001~(0.1) + 0.00110011001100110~(0.2)
그리고 2진수 덧셈의 결과를 반올림하고 10진수로 변환하면..?
0.30000000000000004라는 값이 덧셈의 결과임을 알 수 있습니다.
Next
생각보다 설명해야하는 내용이 길어져 글을 2개로 나눌려고 합니다.
추가적으로 궁금한 것들, 더 설명하고싶은 내용들은 다음 글에서 다루겠습니다.
더 궁금한 것들
- 찾아보면서 자주 보였던 IEEE 754 표준이란?
- BigDecimal 클래스는 어떻게 정확하게 실수를 저장하지?
- 왜 System.out.println(0.1)의 출력은 그대로 0.1이 나오지?
( 부족한 부분이나 수정해야 할 부분을 알려주시면 매우 압도적으로 감사하겠습니다! )
참고자료
'CS' 카테고리의 다른 글
SOLID원칙의 간단한 정리 (0) | 2024.06.23 |
---|---|
C언어의 컴파일 과정 (1) | 2023.12.22 |
소켓이란? (0) | 2023.12.19 |
자바 인코딩의 원리 (2) | 2023.11.08 |
API (Application Programming Interface ) 개념 설명 (0) | 2023.06.21 |