2008-10-07 4 views
69

뺄셈과 같은 문제가 발생하고 결과 값이 잘못되었습니다.자바 라운딩 이중 문제를 해결하는 방법

double netToCompany = targetPremium.doubleValue() - tempCommission; 

708.75 * 10.0/100D

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d; 

78.75 = 7875 = 7875 - 78.75

double dCommission = request.getPremium().doubleValue() - netToCompany; 

877.8499999999999 = 1586.6-708.75

생성 된 기대 값은 877.85 것이다.

정확한 계산을 위해 무엇을해야합니까?

답변

84

부동 소수점 연산의 정밀도를 제어하려면 java.math.BigDecimal을 사용해야합니다. 자세한 내용은 John Zukowski가 The need for BigDecimal을 읽으십시오.

예를 들어, 마지막 줄은 BigDecimal을 사용하여 다음과 같이됩니다.

import java.math.BigDecimal; 

BigDecimal premium = BigDecimal.valueOf(1586.6d); 
BigDecimal netToCompany = BigDecimal.valueOf(708.75d); 
BigDecimal commission = premium.subtract(netToCompany); 
System.out.println(commission + " = " + premium + " - " + netToCompany); 

결과는 다음과 같습니다.

877.85 = 1586.6 - 708.75 
+7

부동 소수점 산술 오류를 모두 피할 수는 없습니다. 숫자를 나타내는 데 사용되는 비트 수는 항상 유한합니다. 당신이 할 수있는 것은 더 높은 정밀도 (비트)를 가진 데이터 타입을 사용하는 것입니다. –

+0

사실입니다. 내 대답을 편집하여 BigDecimal의 사용을보다 정확하게 반영 할 것입니다. –

+4

BigDecimal_division_가 +, -, *와 다르게 취급되어야한다는 메모를 추가하겠습니다. 기본적으로 정확한 값 (1/3 등)을 반환 할 수 없으면 예외가 발생하기 때문입니다. 비슷한 상황에서 BigDecimal.valueOf (a) .divide (BigDecimal.valueOf (b), 25, RoundingMode.HALF_UP) .doubleValue() 여기서 25는 정밀도의 최대 자릿수 (double 결과에서 필요한 것 이상). –

3

this question에 대한 응답을 참조하십시오. 기본적으로 볼 때 부동 소수점 산술을 사용하면 자연스러운 결과를 얻을 수 있습니다.

당신이 편안하게 할 수 있다면 임의의 정밀도 (입력의 유효 자릿수?)를 선택하고 결과를 반올림 할 수 있습니다.

6

복식으로 계산할 때마다 이런 일이 발생할 수 있습니다. 이 코드는 877.85를 제공합니다 :

double answer = Math.round (dCommission * 100000)/100000.0;

+1

나은 분할 한정되지로서 아직 JScience를 사용하는 대신 100000의 100000.0 단계; Math.round는 long을 반환하므로 정수 분할을 사용해야합니다. –

4

달러가 아닌 센트 수를 저장하고 출력 할 때 달러 형식으로 지정하십시오. 그렇게하면 정밀도 문제가없는 정수를 사용할 수 있습니다.

+0

여기서 유일한 단점은 프로세스의 초기 단계에서 분의 1 센트가 손실 될 수 있다는 것입니다. 이는 금융 응용 프로그램에 좋지 않을 수 있습니다. 이것이 문제라면 1/10 센트 또는 필요한 정밀도를 저장할 수 있습니다. –

+0

시간을 거슬러 올라가서 내 과거 - 자기 자신에게 "속임수"를 줄 수 있다면, 이것은 그럴 것입니다. 페니 가격! (또는 1e-3 또는 1e-6 - 여전히 int로 2 백만 달러에 좋은) – Trenton

10

또 다른 예 :

double d = 0; 
for (int i = 1; i <= 10; i++) { 
    d += 0.1; 
} 
System.out.println(d); // prints 0.9999999999999999 not 1.0 

사용 BigDecimal를 대신.

편집 :

또한, 단지 이것은 '자바'반올림 문제가되지 않습니다 지적하는. 다른 언어는 과 유사하지만 반드시 일치하지는 않습니다. Java는 적어도 이와 관련하여 일관된 동작을 보장합니다.

53

이전 답변에서 언급했듯이 부동 소수점 연산의 결과입니다.

이전 포스터 제안대로 숫자 계산을 수행 할 때는 java.math.BigDecimal을 사용하십시오.

그러나, BigDecimal을 사용하는 것이 있습니다.double 값에서 BigDecimal으로 변환 할 때 새로운 BigDecimal(double) 생성자 또는 BigDecimal.valueOf(double) 정적 팩터 리 메서드를 사용할 수 있습니다. 정적 팩토리 메소드를 사용하십시오. 정적 공장 효과적으로 String로 변환하면서

이중 생성자는 BigDecimaldouble의 전체 정밀도를 변환하고 변환하는 BigDecimal에 그.

이러한 미세한 반올림 오류가 발생하면 관련이 있습니다. 숫자는 .585로 표시 될 수 있지만 내부적으로 값은 '0.58499999999999996447286321199499070644378662109375'입니다. BigDecimal 생성자를 사용한 경우 정적 메서드는 0.585와 같은 값을 제공하지만 그렇지 않은 숫자는 0.585가 아닙니다. 내 시스템에

 
double value = 0.585; 
System.out.println(new BigDecimal(value)); 
System.out.println(BigDecimal.valueOf(value)); 

 
0.58499999999999996447286321199499070644378662109375 
0.585 
+3

나는이 문제를 여러 번 접했고 정말 짜증나! – Richard

+1

일 절약합니다. 고맙습니다! –

+3

그 이유는, static 메소드가 무제한의 MathContext를 실질적으로 사용하고있는 것에주의 해주세요. 새로운 BigDecimal (값, 새로운 MathContext (0, RoundingMode.HALF_UP))) –

7

내가 다음과 같이 위의 예제를 수정하는 것입니다 제공 :

import java.math.BigDecimal; 

BigDecimal premium = new BigDecimal("1586.6"); 
BigDecimal netToCompany = new BigDecimal("708.75"); 
BigDecimal commission = premium.subtract(netToCompany); 
System.out.println(commission + " = " + premium + " - " + netToCompany); 

이 방법 당신이로 시작하는 문자열을 사용하는 함정을 피할 수 있습니다. 또 다른 대안 :

import java.math.BigDecimal; 

BigDecimal premium = BigDecimal.valueOf(158660, 2); 
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2); 
BigDecimal commission = premium.subtract(netToCompany); 
System.out.println(commission + " = " + premium + " - " + netToCompany); 

나는 이러한 옵션은 두 배를 사용하는 것보다 더 나은 생각합니다. webapps에서 숫자는 어쨌든 문자열로 시작됩니다.

1

지금까지 자바에서 그렇게 할 수있는 가장 우아하고 가장 효율적인 방법 : 당신이 정확한 계산을 위해 두 배를 사용하지 않아야하지만 어쨌든 결과를 반올림하는 경우

double newNum = Math.floor(num * 100 + 0.5)/100; 
0
double rounded = Math.rint(toround * 100)/100; 
-1

는 다음과 같은 트릭이 나에게 도움이 .

public static int round(Double i) { 
    return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001)); 
} 

예 :

Double foo = 0.0; 
    for (int i = 1; i <= 150; i++) { 
     foo += 0.00010; 
    } 
    System.out.println(foo); 
    System.out.println(Math.round(foo * 100.0)/100.0); 
    System.out.println(round(foo*100.0)/100.0); 

인쇄 :

0.014999999999999965 
0.01 
0.02 

상세 정보 : http://en.wikipedia.org/wiki/Double_precision

+0

.5 *는 2 진수 분수의 경우이 값이 유효한 유일한 경우는 값이 합법적으로 다른 경우입니다. –

+0

@ 마이클 : 내 예제에 결함이 있습니다. 그러나 문제는 여전히 남아 있습니다. 그러므로 나는 예제를 추가했다. – Timon

+3

값이 실제로 0 일 때 메서드가 잘못된 결과를 반환한다는 문제가 남아 있습니다.01499999999 - 이런 문제를 해결하는 것은 근본적으로 잘못된 길입니다. –

-3

그것은 매우 간단합니다.

출력에 % .2f 연산자를 사용하십시오. 문제 해결됨! 예를 들어

:

int a = 877.8499999999999; 
System.out.printf("Formatted Output is: %.2f", a); 

상기 코드의 인쇄 출력 결과 877.85

은 % .2f 연산자는 단지 소수 둘째 자리를 사용하는 것을 정의한다.

+0

이것은 mantisse 근사 및 반올림 문제를 해결하지 못합니다! 당신이 보는 것은 당신이하는 일이 아닙니다! – Benj

+0

10 진수로 반올림하면 877.8을 얻게됩니다. 아마 너를 원해서는 안된다. – mortensi

3

이것은 재미있는 문제입니다.

Timons의 답변은 법적인 배수가 될 수있는 최소 정밀도를 나타내는 엡실론을 지정하는 것입니다. 응용 프로그램에서 0보다 작은 정밀도가 절대로 필요 없다는 것을 알고있는 경우.00000001 그러면 그가 제안한 것은 진실에 매우 근접한 더 정확한 결과를 얻기에 충분합니다. 최대 정밀도를 미리 알고있는 응용 프로그램에서 유용합니다. (통화 정밀도에 대한 인스턴스 금융의 경우)

그러나 근본적인 문제는 반올림을 시도 할 때 요인을 기준으로 나누면 실제로 도입됩니다. 정밀도 문제에 대한 또 다른 가능성. 복식을 조작하면 주파수 변화에 따른 부정확 한 문제가 발생할 수 있습니다. 여기에 목표는 반올림입니다

System.out.println(round((1515476.0) * 0.00001)/0.00001); 

1499999.9999999998가 발생합니다 : 당신은 매우 중요한 자리에서 라운드에 노력하고 특히 경우가 Timons 코드를 다음 실행하면 예를 들어 (그래서 당신의 피연산자는 < 0이다) 500000 단위로 (즉, 1500000을 원한다면)

부정확도를 제거한 유일한 방법은 BigDecimal을 통해 스케일 오프하는 것입니다. 예 :

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue()); 

엡실론 전략과 BigDecimal 전략을 혼합하여 사용하면 정밀도를 정밀하게 제어 할 수 있습니다. 엡실론이라는 아이디어는 당신을 매우 가깝게 만든 다음 BigDecimal은 나중에 다시 스케일 조정하여 발생하는 부정확성을 제거합니다. BigDecimal을 사용하면 응용 프로그램의 예상 성능이 저하되지만

최종 분할에서 오류를 다시 입력 할 수있는 입력 값이 없다는 것을 결정할 수있는 경우에는 크기 조정을 위해 BigDecimal을 사용하는 마지막 단계가 항상 필요한 것은 아니라는 점이 지적되었습니다. 현재 나는 이것을 어떻게 적절히 결정할 수 있을지 모르겠다. 그래서 누군가가 어떻게 그것에 대해 듣고 기뻐할 지 안다면.

2

나은의 BigDecimal이 상당히 (예를 들어, 어떤 SQRT 함수)

double dCommission = 1586.6 - 708.75; 
System.out.println(dCommission); 
> 877.8499999999999 

Real dCommissionR = Real.valueOf(1586.6 - 708.75); 
System.out.println(dCommissionR); 
> 877.850000000000 
+0

lib에 감사드립니다! 하지만 간단한 일로 인해 과부하가되지는 않습니까? – Benj

+0

@Benj 당신은 Guava에 관해서도 똑같이 말할 수 있습니다;) – Tomasz