Pergunta

Esta questão já tem uma resposta aqui:

Parece que a subtração está provocando algum tipo de problema eo valor resultante é errado.

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 = 1.586,6-708,75

O valor esperado resultante seria 877,85.

O que deve ser feito para garantir o cálculo correto?

Foi útil?

Solução

Para controlar a precisão da aritmética de ponto flutuante, você deve usar java.math.BigDecimal . Leia A necessidade de BigDecimal por John Zukowski para mais informações.

Dado o seu exemplo, a última linha seria como seguir 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);

Isto resulta na seguinte saída.

877.85 = 1586.6 - 708.75

Outras dicas

Como as respostas anteriores declarou, esta é uma conseqüência de fazer aritmética de ponto flutuante.

Como um cartaz anterior sugeriu, Quando você está fazendo cálculos numéricos, o uso java.math.BigDecimal.

No entanto, há uma pegadinha de usar BigDecimal. Quando você está convertendo a partir do valor duplo para um BigDecimal, você tem a opção de utilizar um novo construtor BigDecimal(double) ou o método de fábrica BigDecimal.valueOf(double) estática. Use o método de fábrica estático.

O construtor dupla converte toda a precisão do double a um BigDecimal enquanto a fábrica estática efetivamente converte para um String, em seguida, convertidos que a um BigDecimal.

Isto torna-se relevante quando você está executando para esses erros de arredondamento sutis. Um número pode exibir como 0,585, mas internamente o seu valor é '0,58499999999999996447286321199499070644378662109375'. Se você usou o construtor BigDecimal, você obteria o número que não é igual a 0,585, enquanto o método estático iria dar-lhe um valor igual a 0,585.

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

no meu sistema dá

0.58499999999999996447286321199499070644378662109375
0.585

Outro exemplo:

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

Use BigDecimal em vez.

EDIT:

Além disso, apenas para apontar este não é um 'Java' arredondamento questão. Outros idiomas apresentam semelhante (embora não necessariamente consistente) comportamento. Java, pelo menos, garantias comportamento consistente a esse respeito.

Gostaria de modificar o exemplo acima da seguinte forma:

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

Desta forma, você evitar as armadilhas do uso de cordas para começar. Outra 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);

Eu acho que essas opções são melhores do que usando duplos. Em webapps números começam como cordas de qualquer maneira.

Toda vez que você fazer cálculos com duplas, isso pode acontecer. Este código lhe daria 877,85:

dupla resposta = Math.round (dCommission * 100000) / 100000,0;

Salvar o número de centavos em vez de dólares, e apenas fazer o formato de dólares quando você saída dele. Dessa forma, você pode usar um inteiro que não sofrem com os problemas de precisão.

Veja respostas para esta questão . Essencialmente, o que você está vendo é uma consequência natural do uso de aritmética de ponto flutuante.

Você poderia escolher alguma precisão arbitrária (dígitos significativos de suas entradas?) E em volta do seu resultado para ele, se você se sentir confortável fazendo isso.

Esta é uma questão de diversão.

A idéia por trás Timons resposta é que você especificar um epsilon que representa a menor precisão uma dupla legal pode ser. Se você sabe em seu aplicativo que você nunca vai precisar de precisão abaixo 0.00000001 então o que ele sugere é suficiente para obter um resultado mais preciso muito perto da verdade. Útil em aplicações onde eles sabem de antemão sua precisão máxima (no exemplo financiamento para precisões de moeda, etc.)

No entanto, o problema fundamental com a tentativa de terminar o dia é que quando você divide por um fator para redefinir a escala que você realmente introduzir uma outra possibilidade de problemas de precisão. Qualquer manipulação de duplas pode introduzir imprecisão problemas com frequência variável. Especialmente se você está tentando rodada em um dígito muito significativo (para que seus operadores são <0), por exemplo, se você executar o seguinte com código Timons:

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

resultará na 1499999.9999999998 onde o objetivo aqui é rodada nas unidades de 500000 (ou seja, queremos 1500000)

Na verdade, a única maneira de ter certeza absoluta que você eliminou a imprecisão é ir através de um BigDecimal para escalar fora. por exemplo.

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

Usando uma mistura de estratégia epsilon ea estratégia BigDecimal lhe dará um bom controle sobre a sua precisão. A idéia é a epsilon você fica muito próximo e, em seguida, o BigDecimal irá eliminar qualquer imprecisão causada por rescaling depois. Embora usando BigDecimal irá reduzir o desempenho esperado de sua aplicação.

Tem sido apontado para mim que a etapa final de usar BigDecimal para redimensionar nem sempre é necessário para alguns casos usa quando você pode determinar que não há nenhum valor de entrada que a divisão final pode reintroduzir um erro. Atualmente eu não sei como determinar corretamente este modo se alguém sabe como, então eu ficaria feliz de ouvir sobre isso.

Até agora, a maneira mais elegante e mais eficiente de fazer isso em Java:

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

Melhor ainda uso JScience como BigDecimal é bastante limitada (por exemplo, não sqrt função)

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;

Embora você não deve usar duplas para cálculos precisos o seguinte truque ajudou-me se você está arredondando os resultados de qualquer maneira.

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

Exemplo:

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

Que impressões:

0.014999999999999965
0.01
0.02

Mais informações: http://en.wikipedia.org/wiki/Double_precision

É muito simples.

Use operador% .2f para a saída. Problema resolvido!

Por exemplo:

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

Os resultados de código acima em uma saída de impressão de: 877,85

O% .2f define operador que apenas duas casas decimais devem ser usadas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top