IT박스

Java Rounding Double 문제를 해결하는 방법

itboxs 2020. 10. 23. 07:42
반응형

Java Rounding Double 문제를 해결하는 방법


이 질문에 이미 답변이 있습니다.

빼기가 어떤 종류의 문제를 유발하고 결과 값이 잘못된 것 같습니다.

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

78.75 = 787.5 * 10.0 / 100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708.75 = 787.5-78.75

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

877.8499999999999 = 1586.6-708.75

결과 예상 값은 877.85입니다.

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


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

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

import java.math.BigDecimal;

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

결과는 다음과 같습니다.

877.85 = 1586.6 - 708.75

이전 답변에서 언급했듯이 이것은 부동 소수점 산술을 수행 한 결과입니다.

이전 포스터에서 제안했듯이 숫자 계산을 할 때 java.math.BigDecimal.

그러나 BigDecimal. double 값에서으로 변환 할 때 BigDecimalBigDecimal(double)생성자 또는 BigDecimal.valueOf(double)정적 팩토리 메서드를 사용할 수 있습니다. 정적 팩토리 방법을 사용하십시오.

double 생성자는의 전체 정밀도 double를 a BigDecimal로 변환 하는 반면 정적 팩토리는이를 효과적으로 a String로 변환 한 다음이를 BigDecimal.

이는 미묘한 반올림 오류가 발생할 때 관련이 있습니다. 숫자는 .585로 표시 될 수 있지만 내부적으로 값은 '0.58499999999999996447286321199499070644378662109375'입니다. BigDecimal생성자 를 사용하면 0.585와 같지 않은 숫자를 얻을 수 있지만 정적 메서드는 0.585와 같은 값을 제공합니다.

이중 값 = 0.585;
System.out.println (new BigDecimal (값));
System.out.println (BigDecimal.valueOf (값));

내 시스템에서

0.58499999999999996447286321199499070644378662109375
0.585

다른 예시:

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는 적어도 이와 관련하여 일관된 동작을 보장합니다.


위의 예를 다음과 같이 수정합니다.

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

이 옵션이 복식을 사용하는 것보다 낫다고 생각합니다. 웹앱에서 숫자는 어쨌든 문자열로 시작합니다.


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

이중 답변 = Math.round (dCommission * 100000) / 100000.0;


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


이 질문에 대한 답변을 참조하십시오 . 본질적으로 여러분이보고있는 것은 부동 소수점 산술을 사용한 자연스러운 결과입니다.

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


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

The idea behind Timons reply is you specify an epsilon which represents the smallest precision a legal double can be. If you know in your application that you will never need precision below 0.00000001 then what he suggests is sufficient to get a more precise result very close to the truth. Useful in applications where they know up front their maximum precision (for in instance finance for currency precisions, etc)

However the fundamental problem with trying to round it off is that when you divide by a factor to rescale it you actually introduce another possibility for precision problems. Any manipulation of doubles can introduce imprecision problems with varying frequency. Especially if you're trying to round at a very significant digit (so your operands are < 0) for instance if you run the following with Timons code:

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

Will result in 1499999.9999999998 where the goal here is to round at the units of 500000 (i.e we want 1500000)

In fact the only way to be completely sure you've eliminated the imprecision is to go through a BigDecimal to scale off. e.g.

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

Using a mix of the epsilon strategy and the BigDecimal strategy will give you fine control over your precision. The idea being the epsilon gets you very close and then the BigDecimal will eliminate any imprecision caused by rescaling afterwards. Though using BigDecimal will reduce the expected performance of your application.

It has been pointed out to me that the final step of using BigDecimal to rescale it isn't always necessary for some uses cases when you can determine that there's no input value that the final division can reintroduce an error. Currently I don't know how to properly determine this so if anyone knows how then I'd be delighted to hear about it.


So far the most elegant and most efficient way to do that in Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;

Better yet use JScience as BigDecimal is fairly limited (e.g., no sqrt function)

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

double rounded = Math.rint(toround * 100) / 100;

Although you should not use doubles for precise calculations the following trick helped me if you are rounding the results anyway.

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

Example:

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

Which prints:

0.014999999999999965
0.01
0.02

More info: http://en.wikipedia.org/wiki/Double_precision


It's quite simple.

Use the %.2f operator for output. Problem solved!

For example:

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

The above code results in a print output of: 877.85

The %.2f operator defines that only TWO decimal places should be used.

참고URL : https://stackoverflow.com/questions/179427/how-to-resolve-a-java-rounding-double-issue

반응형