Converta float em double sem perder a precisão

97

Eu tenho um flutuador primitivo e preciso como um duplo primitivo. Simplesmente lançar o flutuador para dobrar me dá uma precisão extra estranha. Por exemplo:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

No entanto, se em vez de lançar, eu produzir o float como uma string e analisar a string como um double, eu consigo o que quero:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

Existe uma maneira melhor do que ir para String e voltar?

Steve Armstrong
fonte

Respostas:

124

Não é que você esteja realmente obtendo precisão extra - é que o flutuador não representou com precisão o número que você pretendia originalmente. O duplo está representando o flutuador original com precisão; toStringestá mostrando os dados "extras" que já estavam presentes.

Por exemplo (e esses números não estão certos, estou apenas inventando), suponha que você tenha:

float f = 0.1F;
double d = f;

Então, o valor de fpode ser exatamente 0,100000234523. dterá exatamente o mesmo valor, mas quando você convertê-lo em uma string, ele "confiará" que é preciso com uma precisão maior, então não arredondará tão cedo, e você verá os "dígitos extras" que já eram lá, mas escondido de você.

Quando você converte para uma string e volta, você acaba com um valor duplo que está mais próximo do valor da string do que o float original era - mas isso só é bom se você realmente acreditar que o valor da string é o que você realmente queria.

Tem certeza de que float / double são os tipos apropriados para usar aqui em vez de BigDecimal? Se você está tentando usar números que têm valores decimais precisos (por exemplo, dinheiro), então BigDecimalé um tipo mais apropriado IMO.

Jon Skeet
fonte
40

Acho que a conversão para a representação binária é mais fácil de entender esse problema.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Você pode ver que o float é expandido para o dobro adicionando 0s ao final, mas que a representação dupla de 0,27 é 'mais precisa', daí o problema.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
fonte
25

Isso se deve ao contrato de Float.toString(float), que diz em parte:

Quantos dígitos devem ser impressos para a parte fracionária [...]? Deve haver pelo menos um dígito para representar a parte fracionária e, além disso, tantos, mas apenas tantos, mais dígitos quantos forem necessários para distinguir exclusivamente o valor do argumento dos valores adjacentes do tipo float. Ou seja, suponha que x seja o valor matemático exato representado pela representação decimal produzida por esse método para um argumento finito diferente de zero f. Então f deve ser o valor flutuante mais próximo de x; ou, se dois valores flutuantes são igualmente próximos de x, então f deve ser um deles e o bit menos significativo do significando de f deve ser 0.

Erickson
fonte
13

Encontrei esse problema hoje e não pude usar refatorar para BigDecimal, porque o projeto é realmente enorme. No entanto, encontrei solução usando

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

E isso funciona.

Observe que chamar result.doubleValue () retorna 5623.22998046875

Mas chamar doubleResult.doubleValue () retorna corretamente 5623,23

Mas não tenho certeza se é uma solução correta.

Andrew
fonte
1
Funcionou para mim. Também é muito rápido. Não sei por que isso não está marcado como uma resposta.
Sameer
Este é o método que uso, e você não precisa da nova parte Float ... Apenas crie o FloatingDecimal com a primitiva. Ele será automaticamente encaixotado ... E também funciona rápido ... Existe uma desvantagem, FloatingDecimal é imutável, então você precisa criar um para cada float ... imagine um código computacional de O (1e10)! !! Claro que BigDecimal tem a mesma desvantagem ...
Mostafa Zeinali
6
A desvantagem dessa solução é que sun.misc.FloatingDecimal é a classe interna da JVM e a assinatura de seu construtor foi alterada no Java 1.8. Ninguém deve usar classes internas em aplicativos da vida real.
Igor Bljahhin
8

Encontrei a seguinte solução:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Se você usar float e double em vez de Float e Double, use o seguinte:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}
GSD.Aaz
fonte
7

Use um em BigDecimalvez de float/ double. Existem muitos números que não podem ser representados como ponto flutuante binário (por exemplo, 0.1). Portanto, você deve sempre arredondar o resultado para uma precisão ou uso conhecido BigDecimal.

Consulte http://en.wikipedia.org/wiki/Floating_point para obter mais informações.

Aaron Digulla
fonte
Vamos dizer que você tem 10 milhões de preços flutuantes em seu cache, você ainda considera o uso de BigDecimal e instanciar objetos únicos, digamos ~ 6 milhões de objetos (para descontar flyweight / imutável) .. ou há mais do que isso?
Sendi_t
1
@Sendi_t Por favor, não use comentários para fazer perguntas complexas :-)
Aaron Digulla
Obrigado por olhar! .. nós usamos carros alegóricos agora, como foi acordado há uma década - estava tentando ver seu ponto de vista sobre esta situação no acompanhamento -. obrigado!
Sendi_t
@Sendi_t Mal-entendido. Não posso responder sua pergunta em 512 caracteres. Por favor, faça uma pergunta apropriada e me envie um link.
Aaron Digulla
1
@GKFX Não existe um "tamanho único" quando se trata de lidar com decimais em computadores. Mas, como a maioria das pessoas não entende, indico-lhes, BigDecimalpois isso detectará a maioria dos erros comuns. Se as coisas estão muito lentas, eles precisam aprender mais e encontrar maneiras de otimizar seus problemas. A otimização prematura é a raiz de todos os males - DE Knuth.
Aaron Digulla de
1

Os carros alegóricos, por natureza, são imprecisos e sempre apresentam "problemas" de arredondamento. Se a precisão for importante, você pode considerar a refatoração de seu aplicativo para usar Decimal ou BigDecimal.

Sim, os flutuantes são computacionalmente mais rápidos do que os decimais devido ao suporte do processador. No entanto, você quer rápido ou preciso?

Eu não
fonte
1
A aritmética decimal também é inexata. (por exemplo, 1/3 * 3 == 0,99999999999999999999999999999) É, obviamente, melhor para representar quantidades decimais exatas como dinheiro, mas para medições físicas não tem nenhuma vantagem.
dan04
2
Mas 1 == 0,99999999999999999999999999999 :)
Emmanuel Bourg
0

Para obter informações, isso vem no Item 48 - Evite flutuar e dobrar quando os valores exatos são exigidos, de Effective Java 2ª edição de Joshua Bloch. Este livro está repleto de coisas boas e definitivamente vale a pena dar uma olhada.

mR_fr0g
fonte
0

Isto funciona?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
fonte
0

Uma solução simples que funciona bem é analisar o duplo da representação da string do flutuador:

double val = Double.valueOf(String.valueOf(yourFloat));

Não é super eficiente, mas funciona!

Alessandro Roaro
fonte