프로그램은 1과 0으로 모든 것을 표현한다. 정수에서는 byte 라는 8비트짜리 자료형에서 3을 표현할 때 0 0 0 0 0 0 1 1 이렇게 표현하듯 소수점도 마찬가지이다. 고정 소수점 방식은 32비트에서 미리 비트수를 정하고, 그 비트만을 활용하여 실수를 표현하는 방식이다.
처음 1비트는 양수, 음수를 구별하며 ( 0일 때 양수, 1일 때 음수 )
다음 15비트는 정수부
다음 16비트는 소수부이다.
2진수로 변환된 수를 그대로 넣으면 되고, 남은 수는 0으로 채운다.
예를들어 7.625를 32비트 고정소수점 방식으로 표현하면, 2^2 + 2^1 + 2^0 + 1/2^1 + 1/2^3 이기 때문에 111.101이 되고,
고정 소수점 방식은 Ada 와 같은 언어에서 사용되며, 소수에 사용할 비트가 16비트로 고정되어있기 때문에 큰 실수를 표현하기 어렵다.
2. 부동 소수점 방식
고정 소수점 방식의 단점을 보완하기 위해 등장한게, 부동 소수점 방식이다.
부동 소수점은 2진수를 정규화 하여 사용한다
float (32비트) 기준
처음 1비트는 양수, 음수를 구별하고
다음 8비트는 지수부
다음 23비트는 가수부로 활용한다.
double (64비트) 기준으로
처음 1비트는 양수, 음수를 구별하고
다음 11비트는 지수부
나머지 52비트는 가수부로 활용한다.
부동 소수점 추가 설명
float를 기준으로 보면, 여기서 정규화라는 것은 2진수를 1.xxx … * 2^n 형태로 나타내는 것을 말한다.
예를 들어 7.625를 부동 소수점 방식으로 나타내보자.
정규화 이전에는 111.101이 나온다.
정규화를 하면, 1.11101 * 2^2 가 된다.
111.101이 1.11101이 되기 위해 소수점을 2번 앞으로 당겼기 때문에, 그만큼의 지수가 생긴것이다.
해당 지수에서 IEEE 754 표준에 의거하여 127이라는 bias를 더한 값으로 2진수를 채워 지수부인 8비트를 완성해야 한다.
따라서 2 + 127은 129가 되므로, 1 0 0 0 0 0 0 1 이 지수부 8비트에 들어가는 것이다.
참고로 11비트를 지수부로 가진 double에서는 1023을 더한다.
이렇게 bias를 더하는 이유는 지수가 음수일 경우를 대비하기 위해서이다.
0.000101 이라는 수를 정규화 하면 1.01 * 2^-4 가 된다. 그러면 -4를 지수부에 표현할 방법이 없으므로, +127을 통해 123을 만들어서 0 1 1 1 1 0 1 1 로 만든다. 그래서 지수 + bias를 더했을 때 0 ~ 127이 나오면 음수, 128 ~ 255가 나오면 양수라고 보면 된다. ( 실제 값은 -127 ~ 0 / 1 ~ 128 )
그리고 뒤에 나머지 가수부 비트에 소수를 이진수 화 한 값을 넣어주면 된다.
7.625는 결국 0 / 1 0 0 0 0 0 0 1 / 1 1 1 1 0 1 0 0 0 0 ……… (32비트까지) 이렇게 표현할 수 있는 것이다.
3. 부동 소수점의 오류
왜 0.1 + 0.2 는 0.3이 아닐까?
float 기준으로 23비트까지 가수부로 활용할 수 있다. 우리가 0.5를 이진수로 바꾸면, 0.1 이고, 0.25를 이진수로 바꾸면 0.01이 되며, 0.125는 0.001이 된다. 이처럼 딱 떨어지는 수라면 부동소수점에서 오류가 발생하지 않는다.
그러나 0.3은 어떨까?
0.3 을 2진수로 변환하면, 0.0100110011 . . . 이렇게 특정 수가 무한하게 반복된다. 그러다가 비트가 다 채워지면 거기서 강제로 끊어지게 되므로, 그 뒤에 값만큼의 오차가 발행하게 된다. 그래서 근사치로 표현되는 것이다.
float에 0.3이 담겼다고 해서, 실제로 0.3의 값이 담긴게 아니라, 0.3과 근접한 값이 담긴 것 뿐이다.
그래서 프로그래밍에선 0.1 + 0.2는 0.3이 아니라고 하는 것이다.
즉, 우리는 실수에 대한 어떤 연산등을 할 때 항상 직관과 다른 결과가 발생할 수 있다는 것을 염두해 두어야 한다.
4. 그럼 소수점 계산은 어떻게 할까?
Java에서는 소수점 계산을 위해 BigDecimal 이라는 클래스를 지원해준다.
이는 금융권처럼 소수 계산을 많이하는 곳에서는 필수적으로 알아야하는데, 여기서 사용되는 메서드나 사용 방법은 다른 포스팅에서 하고, 오늘은 어떻게 BigDecimal이 부동소수점의 오류를 보완하는 지를 알아보고자 한다.
예를 들어 100.234 라는 값을 BigDecimal에 담는다.
intVal : 정수부 값, = null
scale : 소수부의 길이 = 3
percision : 정수부 소수부의 길이(length)의 합 = 6
stringCache : 숫자를 String으로 변환한 값 = “100.234”
intCompace : 소수점을 제외한 전체 수 = 100234
즉, String과 어디에 소수점이 찍혔는지 등을 저장하고, 마치 정수형처럼 계산할 수 있게 해주는 것이다.
예시
100.235 + 100.235를 한다고 치자
그러면 정수부분인 100 + 100을 따로 해주고, 소수부인 0.235 와 0.235를 정수처럼 235 235로 만들어 + 로 계산을 해준다.
그러면 200과 470이 나오고, 이걸 소수점을 찍어 합쳐주면 200.470 이 나오게 된다.