float, double, 부동소수점

maketheworldwise·2022년 1월 10일
1
post-custom-banner


이 글의 목적?

지금까지 float, double을 실수를 표현하는 자료형으로만 생각하고 연산에 사용해왔다. 하지만 최근 "금융 계산과 같은 정확한 결과를 원한다면 BigDecimal, int, long을 사용해야 한다." 라는 글을 보게 되었고 float, double를 사용하기 이전에 정확하게 이해하고 넘어가야겠다고 생각했다. 자! 정리해보자. 🙂

실수를 표현하는 방법

실수를 표현하는 방법은 고정 소수점 방식과 부동 소수점 방식 이 있다. 정확도는 고정 소수점 방식이 높지만 대부분 컴퓨터는 부동 소수점 방식을 이용한다.

고정 소수점 방식은 소수점에 이용하는 범위를 고정해놓기 때문에 수의 표현 범위가 크지 않다. 반면 부동 소수점은 소수점을 이용하지 않으면 그 만큼 다른 수를 표현할 수 있도록 더 넓은 표현 범위를 가진다. 따라서 더 넓은 범위를 표현할 수 있는 부동 소수점 방식을 많이 이용한다.

부동 소수점을 살펴보자

부동 소수점 자료형에는 대표적으로 float, double이 있다. float는 32비트, double은 64비트다.

float: 부호(1자리) + 지수(8자리) + 가수(23자리) = 32비트
double: 부호(1자리) + 지수(11자리) + 가수(52자리) = 64비트

부동 소수점 방식은 고정 소수점 방식보다 표현할 수 있는 값의 범위는 넓지만, 정밀도의 문제가 있다. 따라서 실수를 부동 소수점 방식으로 표현하더라도 오차가 존재한다는 점을 유의해야한다.

double value1 = 12.23;
double value2 = 24.45;

System.out.println(value1 + value2);

위 예시 코드의 결과는 "46.68000000000001"이 출력된다. 즉, 실수 연산에서는 소수점 단위 값을 정확히 표현하는 것이 아닌 근사값으로 처리하기 때문에 오차가 발생할 수 있다.

결론적으로 정확한 계산이 필요할 경우에는 이 두 개의 자료형을 사용하지 않는다. 32비트와 64비트로 제공할 수 있는 범위를 넘어서면 그 값의 정확성을 보장하지 못하기 때문에 정확한 계산을 사용할 때는 java.math.BigDecimal 클래스를 사용해야한다.

실수를 메모리에 표현하는 방법

실수를 메모리에 저장하는 것은 간단하다. 가수부와 지수부를 각각 필드에 맞게 할당하면 되기 때문이다. 실수는 일반적으로 정수부와 소수부로 나뉘지만, 가수부와 지수부로 나누어 표현할 수 있다. 부동 소수점 방식은 하나의 실수를 가수부와 지수부로 나누어 표현한다.

±(1.가수부)×2^지수부-127

내가 읽은 레퍼런스에서 "-118.625"라는 수를 부동 소수점으로 변환하는 예를 가져와보자.

1. 음수이기에 최상위 비트를 1로 설정해준다.

2. 절대값 "118.625"를 이진법으로 변환해준다.

# 정수부 변환
118
= 1110110

# 소수부 변환
0.625
= 0.625 x 2 = 1.250 -> 정수부 1
= 0.250 x 2 = 0.500 -> 정수부 0
= 0.500 x 2 = 1.000 -> 정수부 1
= 101

# 결과
118.625 
= 1110110.101

3. 소수점을 이동시켜 정수부가 한자리가 되도록 변환해준다. 자릿수만큼을 2의 지수로 사용하여 곱해주는데 이를 부동 소수점이라고 한다.

1110110.101 -> 1.110110101 x 2^6

4. 소수점 아래 부분이 가수부가 되도록 나머지 비트(23bit)를 0으로 채워준다.

1101 1010 1000 0000 0000 000 x 2^6

5. 4번의 23bit를 가수부로 설정해준다.

6. 32bit IEEE 754 형식에는 bias 라는 고정값이 존재하는데, 이는 127이며 bias를 2의 지수인 6에 더하고 이진수로 변환해준다.

6 + 127 
= 133
= 100000101

7. 6번의 8bit를 지수부로 설정해준다.

BigDecimal을 사용해보자

앞에서 언급했듯, 실수를 다루는 double 타입은 사칙 연산시 내부적으로 수를 저장할 때 이진수의 근사치를 저장하기 때문에 원하는 결과를 못얻어낸다. 그러면 어떻게 해결해야할까? 답은 BigDecimal다.

BigDecimal 내부 코드를 살펴보면 BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN 같이 자주 사용하는 값을 상수로 선언해주고 있다.

초기화해주는 방법은 조금 특별한데, 직접 double 타입으로 초기화해줄 경우 다른 값을 가지게 되어 문자열 형태로 생성자에 전달하여 초기화를 해주어야 한다.

BigDecimal num = new BigDecimal("0.01");

BigDecimal에서 제공해주는 메소드들을 간략하게 살펴보자.

// 최대, 최소
num1.min(num2);
num1.max(num2);

// 소수점 맨 끝의 0 까지 비교
num1.equals(num2);

// 소수점 맨 끝의 0을 무시하고 비교
num1.compareTo(num2);

// 사칙연산
num1.add(num2);
num1.subtract(num2);
num1.multiply(num2);
num1.divide(num2);
num1.remainder(num2);

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓
post-custom-banner

0개의 댓글