Mantenha a precisão com o dobro em Java


Respostas:


151

Como outros já mencionaram, você provavelmente desejará usar a BigDecimalclasse, se quiser ter uma representação exata de 11.4.

Agora, uma pequena explicação sobre por que isso está acontecendo:

Os tipos floate doubleprimitivo em Java são números de ponto flutuante , onde o número é armazenado como uma representação binária de uma fração e um expoente.

Mais especificamente, um valor de ponto flutuante de precisão dupla, como o doubletipo, é um valor de 64 bits, em que:

  • 1 bit indica o sinal (positivo ou negativo).
  • 11 bits para o expoente.
  • 52 bits para os dígitos significativos (a parte fracionária como um binário).

Essas partes são combinadas para produzir uma doublerepresentação de um valor.

(Fonte: Wikipedia: precisão dupla )

Para obter uma descrição detalhada de como os valores de ponto flutuante são manipulados em Java, consulte a Seção 4.2.3: Tipos, formatos e valores de ponto flutuante da especificação da linguagem Java.

Os byte, char, int, longtipos são ponto fixo de números, que são representions exactas de números. Diferentemente dos números de ponto fixo, os números de ponto flutuante algumas vezes (é seguro assumir "na maioria das vezes") não poderão retornar uma representação exata de um número. Esta é a razão pela qual você acaba 11.399999999999como resultado de 5.6 + 5.8.

Ao exigir um valor exato, como 1,5 ou 150.1005, você desejará usar um dos tipos de ponto fixo, que será capaz de representar o número exatamente.

Como já foi mencionado várias vezes, Java possui uma BigDecimalclasse que manipulará números muito grandes e números muito pequenos.

Na Referência da API Java da BigDecimalclasse:

Números decimais assinados com precisão arbitrária e imutável. Um BigDecimal consiste em um valor inteiro não escalonado de precisão arbitrária e uma escala inteira de 32 bits. Se zero ou positivo, a escala é o número de dígitos à direita do ponto decimal. Se negativo, o valor não escalado do número é multiplicado por dez à potência da negação da escala. O valor do número representado pelo BigDecimal é, portanto, (unscaledValue × 10 ^ -scale).

Existem muitas perguntas sobre o Stack Overflow relacionadas à questão dos números de ponto flutuante e sua precisão. Aqui está uma lista de perguntas relacionadas que podem ser interessantes:

Se você realmente quer se aprofundar nos mínimos detalhes dos números de ponto flutuante, dê uma olhada no que todo cientista da computação deve saber sobre aritmética de ponto flutuante .


3
De fato, geralmente existem 53 bits significativos porque o 1 antes do ponto "decimal" está implícito para todos os valores, exceto os desnormalizados, fornecendo um pouco mais de precisão. por exemplo, 3 é armazenado como (1.) 1000 ... x 2 ^ 1 enquanto 0,5 é armazenado como (1.) 0000 ... x 2 ^ -1 Quando o valor é desnormalizado (todos os bits do expoente são zero), pode, e geralmente haverá menos dígitos significativos, por exemplo, 1 x 2 ^ -1030 é armazenado como (0) 00000001 x 2 ^ -1022, para que sete dígitos significativos tenham sido sacrificados em escala.
Sarah Phillips

1
Note-se que, embora BigDecimalseja muito mais lento do que doubleneste caso, não é necessário, pois o dobro tem 15 casas decimais de precisão, você só precisa de arredondamentos.
Peter Lawrey

2
@PeterLawrey Possui 15 dígitos decimais de precisão, se estiverem todos antes do ponto decimal. Tudo pode acontecer após o ponto decimal, devido à incomensurabilidade das frações decimais e binárias.
Marquês de Lorne

@EJP Você está certo, possui cerca de 15 dígitos significativos de precisão. Pode ser 16, mas é mais seguro assumir que é 15 ou talvez 14.
Peter Lawrey

A correção de @PeterLawrey EJP foi devido à minha pergunta: stackoverflow.com/questions/36344758/… você poderia expandir por que não são exatamente 15 e que situações podem ser 16 ou 14?
Shivam Sinha 04/04

103

Quando você digita um número duplo, por exemplo, 33.33333333333333o valor que você obtém é, na verdade, o valor mais próximo da precisão dupla representável, exatamente:

33.3333333333333285963817615993320941925048828125

Dividir isso por 100 dá:

0.333333333333333285963817615993320941925048828125

que também não é representável como um número de precisão dupla, então é arredondado novamente para o valor representável mais próximo, que é exatamente:

0.3333333333333332593184650249895639717578887939453125

Quando você imprime esse valor, ele é arredondado novamente para 17 dígitos decimais, fornecendo:

0.33333333333333326

114
Para quem está lendo isso no futuro e está intrigado com o motivo pelo qual a resposta não tem nada a ver com a pergunta: algum moderador decidiu mesclar a pergunta que eu (e outros) respondemos com essa pergunta, bastante diferente.
Stephen Canon

Como você sabe o valor duplo exato?
Michael Yaworski

@mikeyaworski pt.wikipedia.org/wiki/Double-precision_floating-point_format Veja exemplos de precisão dupla
Jaydee

23

Se você deseja apenas processar valores como frações, pode criar uma classe Fraction que contém um campo numerador e denominador.

Escreva métodos para adicionar, subtrair, multiplicar e dividir, bem como um método toDouble. Dessa forma, você pode evitar flutuações durante os cálculos.

EDIT: implementação rápida,

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}

1
Certamente numeratore denominatordeve ser ints? Por que você deseja precisão de ponto flutuante?
Samir Talwar

Acho que não é realmente necessário, mas evita a transmissão na função toDouble para que o código seja melhor.
Viral Shah

5
ViralShah: Ele também introduz erros de ponto flutuante ao lidar com operações matemáticas. Dado que o objetivo deste exercício é evitar exatamente isso, parece prudente alterá-lo.
Samir Talwar

Editado para usar ints em vez de duplos, pelos motivos mencionados por Samir Talwar acima.
Viral Shah

3
Essa implementação de frações apresenta problemas, pois não os reduz para uma forma mais simples. 2/3 * 1/2 fornecem 2/6 onde você realmente deseja que a resposta seja 1/3. Idealmente, no construtor, você deseja encontrar o MDC do numerador e do divisor e dividir os dois por isso.
Salix alba

15

Observe que você teria o mesmo problema se usasse a aritmética decimal de precisão limitada e desejasse lidar com 1/3: 0,333333333 * 3 é 0,99999999999, e não 1,0000000000.

Infelizmente, 5.6, 5.8 e 11.4 simplesmente não são números redondos em binário, porque envolvem quintos. Portanto, a representação flutuante deles não é exata, assim como 0,3333 não é exatamente 1/3.

Se todos os números usados ​​forem decimais não recorrentes e você desejar resultados exatos, use BigDecimal. Ou, como outros já disseram, se seus valores são como dinheiro, no sentido de que são todos múltiplos de 0,01 ou 0,001, ou algo assim, multiplique tudo por uma potência fixa de 10 e use int ou long (adição e subtração são trivial: cuidado com a multiplicação).

No entanto, se você está satisfeito com o binário para o cálculo, mas deseja imprimir as coisas em um formato um pouco mais amigável, tente java.util.Formatterou String.format. No formato string, especifique uma precisão menor que a precisão total de um duplo. Para 10 números significativos, digamos, 11.399999999999 é 11,4, portanto o resultado será quase tão preciso e legível por humanos nos casos em que o resultado binário estiver muito próximo de um valor que requer apenas algumas casas decimais.

A precisão a especificar depende um pouco da quantidade de matemática que você fez com seus números - em geral, quanto mais você faz, mais erros se acumulam, mas alguns algoritmos a acumulam muito mais rápido que outros (eles são chamados de "instáveis" como oposta a "estável" em relação a erros de arredondamento). Se tudo o que você está fazendo é adicionar alguns valores, acho que soltar apenas uma casa decimal de precisão resolverá o problema. Experimentar.


3
Não, não use o dobro com valores monetários! Você precisa de precisão com dinheiro, use BigDecimal. Caso contrário, sua resposta é boa. Qualquer coisa com a qual você precise de precisão, use BigDecimal; se a precisão não for tão importante, você poderá usar float ou double.
MetroidFan2002 27/11/2008

1
A questão não afirma mais ou implica que há dinheiro envolvido. Eu digo especificamente para usar BigDecimal ou números inteiros por dinheiro. Qual é o problema?
Steve Jessop

1
E igual a "não use o dobro por dinheiro" é "não use BigDecimal ou o dobro por terços". Mas, às vezes, um problema envolve divisão, em que casos todas as bases não divisíveis por todos os fatores primos de todos os denominadores são igualmente ruins.
Steve Jessop

1
0,9999 = 1 se a sua precisão é menos de 4 dígitos significativos
Brian Leahy

9

Você pode querer usar a classe java.math.BigDecimal do java, se realmente precisa de matemática de precisão. Aqui está um bom artigo da Oracle / Sun sobre o caso do BigDecimal . Embora você nunca possa representar 1/3 como alguém mencionado, você pode decidir exatamente o quão preciso você deseja que o resultado seja. setScale () é seu amigo .. :)

Ok, porque tenho muito tempo em minhas mãos no momento, aqui está um exemplo de código relacionado à sua pergunta:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

e para conectar meu novo idioma favorito, Groovy, aqui está um exemplo mais claro da mesma coisa:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333

5

Certamente você poderia ter transformado isso em um exemplo de três linhas. :)

Se você deseja precisão exata, use BigDecimal. Caso contrário, você pode usar ints multiplicados por 10 ^ qualquer precisão que desejar.


5

Como outros observaram, nem todos os valores decimais podem ser representados como binários, pois o decimal é baseado nas potências de 10 e o binário é baseado nas potências de dois.

Se a precisão importa, use BigDecimal, mas se você deseja apenas resultados amigáveis:

System.out.printf("%.2f\n", total);

Darei à você:

11.40


5

Você não pode, porque 7.3 não tem uma representação finita em binário. O mais próximo que você pode chegar é 2054767329987789/2 ** 48 = 7,3 + 1/1407374883553280.

Dê uma olhada em http://docs.python.org/tutorial/floatingpoint.html para obter mais explicações. (Está no site do Python, mas Java e C ++ têm o mesmo "problema".)

A solução depende de qual é exatamente o seu problema:

  • Se você simplesmente não gosta de ver todos esses dígitos de ruído, corrija a formatação da string. Não exiba mais de 15 dígitos significativos (ou 7 para flutuar).
  • Se é que a inexatidão de seus números está quebrando coisas como "if", então você deve escrever if (abs (x - 7.3) <TOLERANCE) em vez de if (x == 7.3).
  • Se você está trabalhando com dinheiro, o que você provavelmente realmente quer é ponto fixo decimal. Armazene um número inteiro de centavos ou qualquer que seja a menor unidade da sua moeda.
  • (MUITO INCRÍVEL) Se você precisar de mais de 53 bits significativos (15 a 16 dígitos significativos) de precisão, use um tipo de ponto flutuante de alta precisão, como BigDecimal.

7.3 pode não ter uma representação finita em binário, mas com certeza recebo -7.3 quando tento a mesma coisa em C ++
wrongusername

2
wrongusername: Não, você não. Apenas mostra dessa maneira. Use o formato "% .17g" (ou, melhor ainda, "% .51g") para ver a resposta real.
dan04

4
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}

3

Use java.math.BigDecimal

Dobros são frações binárias internamente, portanto, às vezes, não podem representar frações decimais no decimal exato.


1
-1 por recomendar cegamente BigDecimal. Se você realmente não precisa de aritmética decimal (ou seja, se estiver fazendo cálculos com dinheiro), o BigDecimal não o ajudará. Ele não resolve todos os erros de ponto flutuante: você ainda precisa lidar com 1/3 * 3 = 0,9999999999999999999999999999 e sqrt (2) ** 2 = 1,999999999999999999999999999. Além disso, o BigDecimal possui uma enorme penalidade de velocidade. Pior ainda, devido à falta de sobrecarga do operador do Java, você precisa reescrever todo o seu código.
dan04

2
@ dan04 - Se você faz cálculos com dinheiro, por que usar representação flutuante sabendo o erro inerente a ele .... Como não há fração de centavos, você pode usar decimal e calcular centavos em vez de usar o dólar aproximado, tem um valor exato em centavos. Se você realmente quer a fração de centavo, use aa longo e calcule milhares de centavos. Além disso, o OP não fez menção a números irracionais, ele só se preocupou com a adição. Leia a publicação com atenção e entenda o problema antes de responder. Isso pode lhe poupar um pouco de vergonha.
Newtopian 18/03/10

3
@ Newtopian: Não tenho nada para me envergonhar. O OP não fez nenhuma menção a dinheiro, nem indicou que seu problema tenha qualquer decimalidade inerente.
dan04

@ Dan04 - Não, o OP não ... Você fez e cegamente oferecido fora da opinião de contexto para o que provavelmente foi uma resposta perfeitamente aceitável dado o mau quantidade de detalhes fornecidos
Newtopian

2

Multiplique tudo por 100 e guarde-o em um centavo.


2
@ Demra - olhe para o post antes da última edição - todo esse material "shoppingTotal" e "calcGST" e "calcPST" parece dinheiro para mim.
Paul Tomblin

2

Os computadores armazenam números em binário e não podem realmente representar números como 33.333333333 ou 100.0 exatamente. Essa é uma das coisas complicadas sobre o uso de duplas. Você precisará arredondar a resposta antes de mostrá-la ao usuário. Felizmente, na maioria dos aplicativos, você não precisa de tantas casas decimais.


Estou fazendo alguns cálculos de probabilidades, preferiria ter a maior precisão possível. Mas eu entendo que existem limitações
Aly

2

Os números de ponto flutuante diferem dos números reais, pois para qualquer número de ponto flutuante existe um próximo número de ponto flutuante mais alto. O mesmo que números inteiros. Não há um número inteiro entre 1 e 2.

Não há como representar 1/3 como um flutuador. Há um flutuador abaixo dele e um flutuador acima dele, e há uma certa distância entre eles. E 1/3 está nesse espaço.

O Apfloat for Java afirma trabalhar com números de ponto flutuante de precisão arbitrária, mas nunca o usei. Provavelmente vale a pena dar uma olhada. http://www.apfloat.org/apfloat_java/

Uma pergunta semelhante foi feita aqui antes biblioteca de alta precisão de ponto flutuante Java


1

Duplos são aproximações dos números decimais na sua fonte Java. Você está vendo a conseqüência da incompatibilidade entre o dobro (que é um valor com código binário) e sua fonte (que é com código decimal).

Java está produzindo a aproximação binária mais próxima. Você pode usar o java.text.DecimalFormat para exibir um valor decimal com melhor aparência.


1

Use um BigDecimal. Ele ainda permite que você especifique regras de arredondamento (como ROUND_HALF_EVEN, que minimizará o erro estatístico ao arredondar para o vizinho par se ambos estiverem na mesma distância; ou seja, 1,5 e 2,5 arredondam para 2).


1

Resposta curta: Sempre use BigDecimal e verifique se você está usando o construtor com String argumento , não o duplo.

De volta ao seu exemplo, o código a seguir imprimirá 11.4, conforme desejado.

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}

0

Confira BigDecimal, ele lida com problemas ao lidar com aritmética de ponto flutuante assim.

A nova chamada ficaria assim:

term[number].coefficient.add(co);

Use setScale () para definir o número de precisão da casa decimal a ser usada.


0

Por que não usar o método round () da classe Math?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

0

Se você não tiver outra opção além de usar valores duplos, poderá usar o código abaixo.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

-1

Não desperdice seu esforço usando BigDecimal. Em 99,99999% dos casos, você não precisa disso. O tipo java double é aproximado, mas em quase todos os casos, é suficientemente preciso. Lembre-se de que você tem um erro no 14º dígito significativo. Isso é realmente insignificante!

Para obter uma boa saída, use:

System.out.printf("%.2f\n", total);

2
Eu acho que ele está preocupado com a saída, não com a precisão numérica. e BigDecimal não ajudariam se você, por exemplo. divida por três. Ele pode até mesmo piorar as coisas ...
Maciek D.

Você nunca deve nunca usar ponto flutuante por dinheiro. Vi grandes retrabalhos sendo executados em um empreiteiro que quebrou essa regra, apesar de receber instruções.
Marquês de Lorne
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.