자바에서 실수 표현 하는 방법
정수 제공하는 실수형 데이터 타입에는 float과 double이 있다. 정수형 int, long 타입과 동일하게 각 4, 8 바이트의 메모리 공간을 활용하지만 실수를 가수와 지수부분으로 나누어 표현하는 부동 소수점 방식을 기반으로 더 넓은 범위의 값을 표현 할 수 있습니다.
컴퓨터는 실수도 2진수로 표현하기 때문에 두가지 방법으로 상대적으로 꽤 복잡하게 동작하게 됩니다. 실수를 표현하는 방식으로는 고정 소수점 방식(Fixed-Point Number Representation)과 부동 소수점 방식(Floating-Point Number Representation)으로 나뉘어 사용이 됩니다.
고정 소수점 방식
고정 소수점 방식(Fixed-Point Number Representation)은 소수점 이상 또는 소수점 이하를 지정하여 처리하는 방식입니다. 즉 소수부의 자릿수를 정하여 고정된 자릿수를 정하여 고정된 자릿수의 소수를 표현합니다.
맨 앞자리 1자리는 부호 비트로 사용된다. 0이면 양수고, 1이면 음수가 됩니다. 나머지 자리의 비트들은 소수점을 기준으로 정수부와 소수부로 나뉘게 됩니다.
부동 소수점 방식
부정 소수점 방식은 고정 소수점과 다르게 표현 됩니다.
- 부호부: 1비트로 0이면 양수 1이면 음수입니다.
- 가수부: 23비트를 양의 정수로 표현합니다.
- 지수부: 8비트를 의미하며, 소수점의 위치를 표현합니다.
문제점
double value1 = 12.23;
double value2 = 34.45;
// 46.68000000000001
value1 + value2;
위와 같이 결과가 46.68이 아닌 실제로는 46.68000000000001이 출력이 됩니다. 이와 같이 실수 연산에서는 정확한 값이 아닌 소수점 단위 값을 정확히 표현하는 것이 아니라 근사값으로 처리하기 때문에 오차가 발생할 수 있습니다. 근사한 차이지만 금융이나 통계가 중요한 기업에는 큰 영향을 줄 수 있습니다.
BigDecimal
이러한 문제를 해결하기 위해 나온 것이 자바에서는 BigDecimal 클래스를 제공하고 있습니다. BigDecimal객체는 사용자가 유효숫자의 개수를 지정할 수 있는 임의 정확도의 십진수를 표현을 합니다. 이클래스는 String 클래스와 같이 불변성을 가지고 있으며 객체를 변경을 할 수 없고 내장 메서드를 통해 새로운 객체를 생성합니다.
// 생성자 + 문자열로 초기화하는 방법
BigDecimal value1 = new BigDecimal("12.23");
// double 타입으로 초기화하는 방법
// 내부적으로 생성자 + 문자열을 사용한다.
BigDecimal value2 = BigDecimal.valueOf(34.45);
// 아래와 같이 사용하면 안 된다.
// 12.230000000000000426325641456060111522674560546875
BigDecimal dontDoThis = new BigDecimal(12.23);
단점
1. 하드웨어는 이진 부동소수점 연산에 최적화 되어있지만 BigDecimal계산은 소프트웨어로 구현된다. 그래서 BigDecimal을 사용하여 많은 계산을 하는 경우 성능 문제를 겪게 될 것이다.
2. BigDecimal에 적용되는 수학 연산자가 없다. 덧셈이나 곱셈도 메소드 호출로 실행해야 한다. 그러므로 쓰고 읽기가 힘들다.
※ 유효숫자 사용
유효 숫자를 사용한다면 아래의 코드 처럼 지정하여 좀 더 정확한 숫자를 계산을 할 수 있습니다. 단 BigDecimal은 값이 완전히 나누어 지지 않는 경우 반올림을 할 소수점을 지정해주지 않으면 ArithmeticException 예외를 던진다
사용예시
double d = ...;
BigDecimal bd = new BigDecimal(d);
bd = bd.round(new MathContext(3));
double rounded = bd.doubleValue();
유효 숫자란?
수의 정확도에 영향을 주는 숫자
1. 0이 아닌 정수는 모두 유효 숫자 입니다.
ex) 35.85 : 유효 숫자 4개
2. 첫 머리에 있는 0은 유효 숫자가 아닙니다.
ex) 0.0064 : 유효숫자 2개
3. 0이 아닌 숫자 사이의 0은 유효 숫자입니다.
ex) 6.0202 : 유효숫자 5개
4. 숫자의 오른쪽 끝부분에 0이 있는 경우, 소숫점과 함께 쓰일 경우 0은 유효숫자
ex) 100. :유효숫자 3개 / 1.00 : 유효숫자 3개
5. 숫자 끝의 0은 유효숫자가 될수도 안될 수도 있다.
ex) 35700 : 과학적 표기법으로 표시
※ 과학적 표기법
지수형태로 유효숫자를 표시하여 수를 나타냄
6 * 10² | 6.0 * 10² | 6.00 * 10² |
1개 | 2개 | 3개 |
유효 숫자 연산
1. 덧셈/뻴셈 : 소수점 이하 자리수가 가장 적은 유효숫자로 제한
ex) 27.19(4개) + 10.275(5개) = 37.465(5개) => 37.47 (반올림을 통해 적은 유효숫자(4개)로 제한 합니다.)
2. 곱셈/나누셈 : 가장 적은 유효 숫자 개수로 제한
ex) 12.34(4개) * 1.23(3개) = 15.1782(6개) -> 15.2(3개 반올림을 하여 유효숫자 3개로 제한 합니다.)
출처
https://madplay.github.io/post/the-need-for-bigdecimal-in-java
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ryu091011&logNo=110032726404
https://stackoverflow.com/questions/7548841/round-a-double-to-3-significant-figures