Domanda

Questa domanda ha già una risposta qui:

Sembra che la sottrazione stia innescando qualche tipo di problema e il valore risultante è sbagliato.

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

Il valore atteso risultante sarebbe 877,85.

Cosa bisogna fare per garantire il calcolo corretto?

È stato utile?

Soluzione

Per controllare la precisione dell'aritmetica in virgola mobile, è necessario utilizzare java.math.BigDecimal . Leggi La necessità di BigDecimal di John Zukowski per ulteriori informazioni.

Dato il tuo esempio, l'ultima riga sarebbe la seguente usando 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);

Ciò si traduce nel seguente output.

877.85 = 1586.6 - 708.75

Altri suggerimenti

Come indicato nelle risposte precedenti, questa è una conseguenza del fare l'aritmetica in virgola mobile.

Come suggerito da un precedente poster, quando si eseguono calcoli numerici, utilizzare java.math.BigDecimal.

Tuttavia, esiste un problema per l'utilizzo di BigDecimal. Quando si esegue la conversione dal doppio valore in BigDecimal(double), è possibile scegliere di utilizzare un nuovo BigDecimal.valueOf(double) costruttore o il double metodo statico di fabbrica. Usa il metodo factory statico.

Il doppio costruttore converte l'intera precisione di String in <=> mentre la factory statica la converte effettivamente in <=>, quindi la converte in <=>.

Questo diventa rilevante quando ci si imbatte in quegli errori di arrotondamento sottili. Un numero potrebbe essere visualizzato come .585, ma internamente il suo valore è '0,58499999999999996447286321199499070644378662109375'. Se si utilizza il costruttore <=>, si otterrà il numero NON uguale a 0,585, mentre il metodo statico fornirebbe un valore pari a 0,585.

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

sul mio sistema dà

0.58499999999999996447286321199499070644378662109375
0.585

Un altro esempio:

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

Usa invece BigDecimal.

EDIT:

Inoltre, solo per sottolineare che questo non è un problema di arrotondamento 'Java'. Mostra di altre lingue comportamento simile (anche se non necessariamente coerente). Java almeno garantisce un comportamento coerente in questo senso.

Modificherei l'esempio sopra come segue:

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

In questo modo eviti le insidie ​​​​dell'uso della stringa per cominciare.Un'altra alternativa:

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

Penso che queste opzioni siano migliori rispetto all'uso dei doppi.Nelle webapp i numeri iniziano comunque come stringhe.

Ogni volta che esegui calcoli con i doppi, questo può succedere. Questo codice ti darebbe 877.85:

doppia risposta = Math.round (dCommission * 100000) / 100000.0;

Salva il numero di centesimi anziché di dollari e fai semplicemente il formato in dollari quando lo produci. In questo modo puoi usare un numero intero che non soffra dei problemi di precisione.

Vedi le risposte a questa domanda . In sostanza ciò che stai vedendo è una conseguenza naturale dell'uso dell'aritmetica in virgola mobile.

Potresti scegliere una precisione arbitraria (cifre significative dei tuoi input?) e arrotondare il risultato ad esso, se ti senti a tuo agio nel farlo.

Questo è un problema divertente.

L'idea alla base della risposta di Timons è quella di specificare un epsilon che rappresenti la più piccola precisione che un doppio legale possa essere. Se nella tua applicazione sai che non avrai mai bisogno di una precisione inferiore a 0,00000001, ciò che suggerisce è sufficiente per ottenere un risultato più preciso molto vicino alla verità. Utile in applicazioni in cui conoscono in anticipo la massima precisione (ad esempio finanziamenti per precisazioni valutarie, ecc.)

Tuttavia il problema fondamentale con il tentativo di arrotondare è che quando si divide per un fattore per ridimensionarlo si introduce effettivamente un'altra possibilità per problemi di precisione. Qualsiasi manipolazione del doppio può introdurre problemi di imprecisione con frequenza variabile. Soprattutto se stai cercando di arrotondare a una cifra molto significativa (quindi i tuoi operandi sono & Lt; 0) ad esempio se esegui quanto segue con il codice di Timons:

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

Si tradurrà in 1499999.9999999998 dove l'obiettivo qui è di arrotondare alle unità di 500000 (ovvero vogliamo 1500000)

In effetti, l'unico modo per essere completamente sicuri di aver eliminato l'imprecisione è passare attraverso un BigDecimal per ridimensionarlo. per es.

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

L'uso di un mix di strategia epsilon e strategia BigDecimal ti darà un controllo eccellente sulla tua precisione. L'idea di essere epsilon ti avvicina molto e quindi BigDecimal eliminerà qualsiasi imprecisione causata dal salvataggio successivo. Sebbene l'utilizzo di BigDecimal ridurrà le prestazioni previste dell'applicazione.

Mi è stato fatto notare che il passaggio finale dell'utilizzo di BigDecimal per ridimensionare non è sempre necessario per alcuni casi di utilizzo quando è possibile determinare che non esiste un valore di input per cui la divisione finale può reintrodurre un errore. Al momento non so come determinarlo correttamente, quindi se qualcuno sa come farebbe piacere sentirne parlare.

Finora il modo più elegante ed efficiente per farlo in Java:

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

Meglio ancora usare JScience poiché BigDecimal è abbastanza limitato (es. no sqrt funzione)

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;

Anche se non dovresti usare i doppi per calcoli precisi, il seguente trucco mi ha aiutato se comunque stavi arrotondando i risultati.

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

Esempio:

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

Quale stampa:

0.014999999999999965
0.01
0.02

Ulteriori informazioni: http://en.wikipedia.org/wiki/Double_precision

È abbastanza semplice.

Utilizzare l'operatore% .2f per l'output. Problema risolto!

Ad esempio:

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

Il codice sopra riportato genera un output di stampa di: 877,85

L'operatore% .2f definisce che devono essere utilizzate solo DUE posizioni decimali.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top