Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

전공공부

아이템 60. 정확한 답이 필요하다면 float와 double형의 사용을 피하라 본문

Study/Java

아이템 60. 정확한 답이 필요하다면 float와 double형의 사용을 피하라

monitor 2023. 1. 15. 20:47

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;
        }
}