본문 바로가기
Develop/etc

0.1 + 1.1 == 1.2 문제 (feat. 부동소수점)

by 독서왕뼝아리 2023. 5. 23.

 

왜 이런 문제가 발생할까?

 


 

실수 표현 방법

  • 고정 소수점 방식 (Fixed Point)

실수는 보통 정수부와 소수부로 나누어 표기한다.

따라서 실수를 표현하는 가장 간단한 방식은 소수부의 자릿수를 미리 정하여, 고정된 자릿수의 소수를 표현하는 것이다.

장점 : 단순하다.

단점 : 표현 범위가 적다.

 

 

  • 부동 소수점 방식(Floating Point)

하나의 실수를 가수부와 지수부로 나누어 표현하는 방식이다. (不動이 아니라 浮動이다)
지수부 : 크기를 표현함. 가수의 어디쯤에 소수점이 있는지 나타냄

가수부 : 실수의 실제값 표현

지수의 값에 따라 소수점이 움직이는 방식을 활용한 실수 표현 방법이다.

float 형
double 형

장점 : 범위가 넓다(현재 대부분 시스템에서 사용 중)

단점 : 오차 발생 가능성

 


 

부동소수점의 오차

1보다 작은 수의 경우에는 십진법으로 간단히 표현되는 수도 이진법에서는 무한개의 유효숫자를 가질 수 있다. 예를 들어 0.1이라는 숫자는 십진수로는 간단히 표현되지만 이진수로 나타내면 다음과 같이 0011(2)이 무한히 반복되는 실수가 된다. 가수부를 이진법 소수점으로 표현하기 때문에 정확한 값을 저장할 수 없다. (0.5, 0.25, 0.125,... 로만 표현 가능)

 

0.1=0.00011001100110011001100110011001100110011001100110011001100110011⋯

 

그런데 컴퓨터에서는 하나의 숫자를 나타내기 위한 메모리 크기가 제한되어 있어서 특정 소수점 이하는 생략하고 가장 비슷한 숫자로 표현할 수 밖에 없다. 0.1은 실제로는 가장 비슷한 다음과 같은 숫자로 저장된다.

 

0.1≈0.1000000000000000055511151231257827021181583404541015625

 

(그런데 파이썬 콘솔이나 주피터 노트북에서 0.1을 입력하면 다음과 같이 그냥 0.1로 나타난다.)

 

 

 

그래서 돈 같은 예민한 값들은 실수형으로 저장하지 않는다. 달러와 센트 같이 소수점 이하는 따로 정수형 데이터로 저장해야 문제가 발생하지 않는다!

 

나는 알고리즘 풀 때 실수형 관련해서 문제도 있었다.

2023.04.30 - [TIL] - [C++] pow함수 double 형의 정확성 문제

 

[C++] pow함수 double 형의 정확성 문제

https://www.acmicpc.net/problem/1740 1740번: 거듭제곱 3의 제곱수를 생각하자. 3의 0제곱, 3의 1제곱, 3의 2제곱, ... 은 순서대로 1, 3, 9, 27, ... 이 된다. 이를 바탕으로, 한 개 이상의 서로 다른 3의 제곱수의

oozoowos.tistory.com

 

 

자바에서 해결법

JAVA에서는 BigDecimal이라는 클래스가 존재한다. BigDecimal클래스의 메소드를 이용하여 사칙연산을 하게 되면 부동소수점이 발생하지 않는다.

BigDeciaml 객체 생성 시 String으로 형 변환하여 생성해야 한다는 것을 주의하자.

BigDecimal val1 = new BigDecimal("1.1");
BigDecimal val2 = new BigDecimal("0.1");

//더하기
System.out.println(val1.add(val2));		//1.2
//빼기
System.out.println(val1.subtract(val2));	//1.0
//곱하기
System.out.println(val1.multiply(val2));	//0.11
//나누기
System.out.println(val1.divide(val2));		//11
double beforeSum = 0;
BigDecimal afterSum = new BigDecimal("0");
for(int cnt = 0 ; cnt < 100 ; cnt++) {
	beforeSum += 0.1;
	afterSum = afterSum.add(new BigDecimal("0.1"));
}
System.out.println("beforeSum : " + beforeSum);	//beforeSum : 9.99999999999998
System.out.println("afterSum : " + afterSum);	//afterSum : 10.0