JAVA 부동소수점 계산 정밀도 손실 베이스 원리와 해결 방안

3999 단어 java부동 소수점
질문:
두 개의 더블 형식의 값을 연산하면 때때로 결과 값이 이상하는 문제가 발생할 수 있다.예:

  System.out.println(19.99+20);
  System.out.println(1.0-0.66);
  System.out.println(0.033*100);
  System.out.println(12.3/100);
출력:

39.989999999999995
0.33999999999999997
3.3000000000000003
0.12300000000000001
Java의 단순 부동소수점 유형인 float와 double는 정확하게 연산할 수 없습니다.이 문제는 사실 JAVA의 버그가 아니다. 컴퓨터 자체가 2진법이고 부동점수는 사실상 근사값이기 때문에 2진법에서 10진법 부동점수로 바뀔 때 정밀도를 잃어버리기 쉬워 정밀도가 떨어진다.
정밀도 손실의 원리에 대해 간단하게 말할 수 있다. 우선 하나의 정수는 컴퓨터에서 01010 형식으로 표시하고 부동점수도 예외가 아니다.
예컨대 11, 11을 2로 나누면 5 여 1이다
5 나누기 2는 2 나머지 1
2를 2로 나누면 1이 되고 0이 된다.
1 을 2로 나누면 0 남짓 1이다
그래서 112진법은 1011이라고 한다.
더블 형식은 8바이트, 64위, 1위는 기호위, 뒤의 11위는 지수 부분, 나머지는 유효한 숫자를 차지한다.
정수를 2로 나누면 틀림없이 끝이 있을 것이다. 그 후에 2진법을 10진법으로 환원하려면 2를 곱하기만 하면 된다.
예를 들면: 0.99용 유효한 숫자 부분,
       0.99 * 2 = 1+0.98 --> 1
       0.98 * 2 = 1+0.96 --> 1
       0.96 * 2 = 1+0.92 -->1
       0.92 * 2 = 1+0.84 -->1
         ...............
이렇게 반복적으로 시작하는 것은 끝이 없고 더블 유효 숫자가 제한되어 있기 때문에 반드시 손실이 있기 때문에 2진법은 0.99를 정확하게 표시할 수 없다. 마치 10진법이 1/3을 정확하게 표시할 수 없는 것과 같다.
해결 방법:
에서 언급한 원칙은 플로트와 더블은 과학 계산이나 공학 계산으로만 사용할 수 있지만 상업 계산에서 우리는java를 사용해야 한다는 것이다.math.Big Decimal, Big Decimal 클래스를 사용하면 상술한 문제를 해결할 수 있다. 우선 주의해야 할 것은 문자열을 직접 사용하여 Big Decimal을 구성하는 것은 절대 정밀도 손실이 없다. 만약에 double 또는 double를string으로 바꾸면 Big Decimal을 구성하는 것은 정밀도 손실이 있기 때문에 이런 해결 방법은 사용 중에 부동점수를string으로 표시하여 저장하는 것이다. 연산을 직접string으로 double를 구성하는 것과 관련된다.그렇지 않으면 틀림없이 정밀도 손실이 있을 것이다.
1. 더하기

/**
 *  
 * @param double1
 * @param double2
 * @return
 */
public static double add(String doubleValA, String doubleValB) { 
  BigDecimal a2 = new BigDecimal(doubleValA); 
  BigDecimal b2 = new BigDecimal(doubleValB); 
  return a2.add(b2).doubleValue(); 
}
2.상감

/**
 *  
 * @param double1
 * @param double2
 * @return
 */
public static double sub(String doubleValA, String doubleValB) { 
  BigDecimal a2 = new BigDecimal(doubleValA); 
  BigDecimal b2 = new BigDecimal(doubleValB); 
  return a2.subtract(b2).doubleValue();
}
3. 곱하기

/**
 *  
 * @param double1
 * @param double2
 * @return
 */
public static double mul(String doubleValA, String doubleValB) { 
  BigDecimal a2 = new BigDecimal(doubleValA); 
  BigDecimal b2 = new BigDecimal(doubleValB); 
  return a2.multiply(b2).doubleValue();
}
4. 상쇄

/**
 *  
 * @param double1
 * @param double2
 * @param scale  
 * @return
 */
public static double div(String doubleValA, String doubleValB, int scale) { 
  BigDecimal a2 = new BigDecimal(doubleValA); 
  BigDecimal b2 = new BigDecimal(doubleValB);
  return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 
}
5. 주 함수 호출

public static void main(String[] args) {
  String doubleValA = "3.14159267";
  String doubleValB = "2.358";
  System.out.println("add:" + add(doubleValA, doubleValB));
  System.out.println("sub:" + sub(doubleValA, doubleValB));
  System.out.println("mul:" + mul(doubleValA, doubleValB));
  System.out.println("div:" + div(doubleValA, doubleValB, 8));
}
결과는 다음과 같이 표시됩니다.

 add:5.49959267
 sub:0.78359267
 mul:7.40787551586
 div:1.33231241
그래서 가장 좋은 방법은 더블을 완전히 버리고string과java를 사용하는 것이다.math.BigDecimal.
java는 IEE가 제정한 부동점수 표현법에 따라float,double 연산을 진행한다.이런 구조는 일종의 과학 계수법으로 기호, 지수와 끝수로 표시하고 밑수를 2로 정한다. 즉, 하나의 부동소수를 끝수로 표시하고 2의 지수를 곱한 다음에 기호를 추가한다.구체적인 밑바닥은 어떻게 저장하고 어떻게 운행하는지 제 블로그를 계속 지켜봐 주십시오. 다음에 상세한 상황을 정리할 것입니다.
이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!

좋은 웹페이지 즐겨찾기