전공공부
아이템 60. 정확한 답이 필요하다면 float와 double형의 사용을 피하라 본문
Why?
정확한 계산을 위해서 부동 소수점의 사용을 피하여야 한다.
예를 들면, 금융 계산이 필요한 경우 굉장히 크리티컬한 이슈를 발생 시킬 수 있는데, 1달러 - 42센트를 계산하기 위해서
Java에서 계산하여 출력해보면
System.out.println(Double.valueOf(1-0.42));
출력 : 0.5800000000000001
이러한 오답을 내게 될 수 밖에 없다.
이유는 부동 소숫점 저장 방식에 따른 오류이다.
Reason
컴퓨터가 실수를 표현하는 방식이 2진수라서 정확한 소수의 표현이 되지 않는다.
컴퓨터가 정수 및 소수를 표현하는 방법은 위와 같다.
그래서, 0.75를 계산하려면 0.25 (1/4) + 0.5 (1/2)로 만드는 것인데 이는 위 방식대로라면 0.11으로 2진수로 표현이 가능하다.
하지만, 263.3의 경우 2진수로 표현시 100000111.010100110011.. 의 반복이 일어난다.
그럼 이것을 자바에서 저장하는 부동 소수점 방식으로 계산하면
100000111.010011001100110... -> 1.00000111010011001100110... 맨 앞에 1을 붙여 주고 계산
부호 비트(1bit) : 0 (양수)
지수 비트(8bit): (127 (뒤로 갈때 대비하여 0부터 시작) + 8) 2^8 승
가수 비트(23bit) : 00000111010011001100110
-> 저장 방식이 결국 가수 비트 23bit에서 멈추므로 누락이 발생 할 수 밖에 없다.
그래서, 정확한 계산을 위해서 저자는 int형이나 BigDecimal의 사용을 권장한다.
Code - Before
/**
* 두 지점 사이의 거리 계산
*
*@ param Integer vtxLon, Integer vtxLat, Integer resultLinkInfoLon, Integer resultLinkInfoLat
*@ return Double distance
*/
private Double getDistance(Integer nvtxLon, Integer vtxLat, Integer resultLinkInfoLon, Integer resultLinkInfoLat){
Double lonA = Double.valueOf(vtxLon) / LON_LAT_CONSTANT * PI / STRAIGHT_ANGLE;
//noinspection WrapperTypeMayBePrimitive
Double latA = Double.valueOf(vtxLat) / LON_LAT_CONSTANT * PI / STRAIGHT_ANGLE;
Double lonB = Double.valueOf(resultLinkInfoLon) / LON_LAT_CONSTANT * PI / STRAIGHT_ANGLE;
//noinspection WrapperTypeMayBePrimitive
Double latB = Double.valueOf(resultLinkInfoLat) / LON_LAT_CONSTANT * PI / STRAIGHT_ANGLE;
Double thetaAngle = acos(sin(latA) *sin(latB) + cos(latA) * cos(latB) * cos(lonB-lonA));
if(isNaN(thetaAngle * EARTH_RADIUS))
{
return 0.0;
}
else {
return thetaAngle * EARTH_RADIUS;
}
}
Code - After
public static final BigDecimal LON_LAT_CONSTANT = new BigDecimal(String.valueOf(3600.0));
//문자열로 지정해야 Double to BigDecimal 시 근소한 차이의 오차를 발생시키지 않으므로 문자열로 지정하였습니다.
public static final BigDecimal PI = new BigDecimal(String.valueOf(3.1415926535897));
public static final BigDecimal STRAIGHT_ANGLE = new BigDecimal(String.valueOf(100.0));
public static final BigDecimal LAT_LON_DIVIDE = (LON_LAT_CONSTANT.multiply(PI)).divide(STRAIGHT_ANGLE,11,BigDecimal.ROUND_CEILING);
//나눗셈의 경우 소숫점을 지정하지 않으면 BigDecimal의 한계 지점까지 도달 할 수 있으므로 끝을 지정하고 올림을 사용하였습니다.
private Double getDistance(Integer vtxLon, Integer vtxLat, Integer resultLinkInfoLon, Integer resultLinkInfoLat){
BigDecimal vtx = new BigDecimal(vtxLon);
Double lonA = vtx.divide(LAT_LON_DIVIDE,11,BigDecimal.ROUND_CEILING).doubleValue(); //Demical 사용하더라도 제약이 존재한다. 11자리수 부터 소수점 버리고 값 올리고 진행
vtx = new BigDecimal(vtxLat);
Double latA = vtx.divide(LAT_LON_DIVIDE,11,BigDecimal.ROUND_CEILING).doubleValue();
vtx = new BigDecimal(resultLinkInfoLon);
Double lonB = vtx.divide(LAT_LON_DIVIDE,11,BigDecimal.ROUND_CEILING).doubleValue();
vtx = new BigDecimal(resultLinkInfoLat);
Double latB = vtx.divide(LAT_LON_DIVIDE,11,BigDecimal.ROUND_CEILING).doubleValue();
// 그나마 BigDecimal 계산 이후 Double로 변환하여 어느정도 손실을 막을 수 있었습니다. (하지만, Double로 변환 됨에 따라서 근소한 손실 값 다시 존재)
Double thetaAngle = acos(sin(latA) *sin(latB) + cos(latA) * cos(latB) * cos(lonB-lonA));
Double AngleMultRadius = new BigDecimal(String.valueOf(thetaAngle)).multiply(new BigDecimal(String.valueOf(EARTH_RADIUS))).doubleValue();
if(isNaN(AngleMultRadius))
{
return 0.0;
}
else {
return AngleMultRadius;
}
}
'Study > Java' 카테고리의 다른 글
아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2023.03.05 |
---|---|
아이템 13. clone 재정의는 주의해서 진행해라 (2) | 2023.02.20 |
아이템 7. 다 쓴 객체 참조를 해제하라 (0) | 2023.02.13 |
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.01.31 |
아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라 (0) | 2023.01.24 |