Konwertuj zmiennoprzecinkowe na podwójne bez utraty precyzji

97

Mam prymitywny pływak i potrzebuję jako prymitywny podwójny. Rzucenie spławika na podwójne daje mi dziwną dodatkową precyzję. Na przykład:

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

Jeśli jednak zamiast rzutowania wyprowadzam zmiennoprzecinkowy jako ciąg i analizuję ciąg jako podwójny, otrzymuję to, czego chcę:

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

Czy jest lepszy sposób niż pójście do String iz powrotem?

Steve Armstrong
źródło

Odpowiedzi:

125

Nie chodzi o to, że faktycznie uzyskujesz dodatkową precyzję - chodzi o to, że pływak nie odzwierciedla dokładnie liczby, do której dążyłeś pierwotnie. Podwójna jest reprezentujący dokładnego pierwotnego przepływu; toStringpokazuje „dodatkowe” dane, które już były obecne.

Na przykład (a te liczby nie są prawidłowe, tylko zmyślam) przypuśćmy, że masz:

float f = 0.1F;
double d = f;

Wtedy wartość fmoże wynosić dokładnie 0,100000234523. dbędzie mieć dokładnie tę samą wartość, ale kiedy przekonwertujesz ją na ciąg, będzie „ufać”, że jest dokładna z większą precyzją, więc nie zaokrągli się tak wcześnie, a zobaczysz „dodatkowe cyfry”, które już były tam, ale ukryte przed tobą.

Kiedy konwertujesz na łańcuch iz powrotem, uzyskujesz wartość double, która jest bliższa wartości ciągu niż oryginalna wartość zmiennoprzecinkowa - ale to dobre tylko wtedy , gdy naprawdę wierzysz, że wartość ciągu jest tym, czego naprawdę chciałeś.

Czy jesteś pewien, że zmiennoprzecinkowe / podwójne są odpowiednimi typami do użycia zamiast BigDecimal? Jeśli próbujesz użyć liczb, które mają dokładne wartości dziesiętne (np. Pieniądze), BigDecimalbardziej odpowiednim typem jest IMO.

Jon Skeet
źródło
40

Uważam, że konwersja do reprezentacji binarnej jest łatwiejsza do zrozumienia tego problemu.

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

Możesz zobaczyć, że zmiennoprzecinkowa jest rozszerzana do podwójnej, dodając 0 na końcu, ale podwójna reprezentacja 0,27 jest „dokładniejsza”, stąd problem.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
źródło
25

Wynika to z umowy Float.toString(float), która mówi po części:

Ile cyfr należy wydrukować dla części ułamkowej […]? Musi istnieć co najmniej jedna cyfra reprezentująca część ułamkową, a poza nią tyle, ale tylko tyle, więcej cyfr, ile potrzeba, aby jednoznacznie odróżnić wartość argumentu od sąsiednich wartości typu float. To znaczy, załóżmy, że x jest dokładną wartością matematyczną reprezentowaną przez reprezentację dziesiętną utworzoną przez tę metodę dla skończonego niezerowego argumentu f. Wtedy f musi być wartością zmiennoprzecinkową najbliższą x; lub jeśli dwie wartości zmiennoprzecinkowe są jednakowo bliskie x, to f musi być jedną z nich, a najmniej znaczący bit znaczenia f musi wynosić 0.

erickson
źródło
13

Napotkałem ten problem dzisiaj i nie mogłem użyć refaktoryzacji do BigDecimal, ponieważ projekt jest naprawdę ogromny. Jednak znalazłem rozwiązanie przy użyciu

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

I to działa.

Zwróć uwagę, że wywołanie result.doubleValue () zwraca 5623.22998046875

Ale wywołanie doubleResult.doubleValue () zwraca poprawnie 5623.23

Ale nie jestem do końca pewien, czy to poprawne rozwiązanie.

Andrzej
źródło
1
Pracował dla mnie. Jest również bardzo szybki. Nie wiem, dlaczego nie jest to oznaczone jako odpowiedź.
Sameer
To jest metoda, której używam i nie potrzebujesz nowej części Float ... Po prostu utwórz FloatingDecimal z prymitywem. Zostanie automatycznie zapakowany ... I działa też szybko ... Jest włączony; jedna wada, FloatingDecimal jest niezmienna, więc musisz utworzyć ją dla każdego pojedynczego float ... wyobraź sobie kod obliczeniowy O (1e10)! !! Oczywiście BigDecimal ma tę samą wadę ...
Mostafa Zeinali,
6
Wadą tego rozwiązania jest to, że sun.misc.FloatingDecimal jest wewnętrzną klasą JVM, a sygnatura jej konstruktora została zmieniona w Javie 1.8. Nikt nie powinien używać klas wewnętrznych w rzeczywistej aplikacji.
Igor Bljahhin
8

Znalazłem następujące rozwiązanie:

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

Jeśli używasz float i double zamiast Float i Double, użyj następujących:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}
GSD.Aaz
źródło
7

Użyj BigDecimalzamiast float/ double. Istnieje wiele liczb, których nie można przedstawić jako binarne liczby zmiennoprzecinkowe (na przykład 0.1). Tak więc zawsze musisz zaokrąglić wynik do znanej dokładności lub użyć BigDecimal.

Więcej informacji można znaleźć pod adresem http://en.wikipedia.org/wiki/Floating_point .

Aaron Digulla
źródło
Powiedzmy, że masz w swojej pamięci podręcznej 10 mln zmiennoprzecinkowych cen handlowych, czy nadal rozważasz użycie BigDecimal i utworzenie instancji unikalnych, powiedzmy, ~ 6 mln obiektów (aby zdyskontować wagę muchową / niezmienną)… czy jest coś więcej?
Sendi_t
1
@Sendi_t Nie używaj komentarzy do zadawania skomplikowanych pytań :-)
Aaron Digulla
Dzięki za opiekę! .. używamy teraz pływaków, tak jak zostało to uzgodnione dziesięć lat temu - starałem się zobaczyć twój punkt widzenia na tę sytuację po dalszych działaniach -. dzięki!
Sendi_t
@Sendi_t Misunderstanding. Nie mogę odpowiedzieć na Twoje pytanie w 512 znakach. Proszę zadać odpowiednie pytanie i przesłać mi link.
Aaron Digulla
1
@GKFX Nie ma jednego rozwiązania dla wszystkich, jeśli chodzi o obsługę liczb dziesiętnych na komputerach. Ale ponieważ większość ludzi tego nie rozumie, wskazuję im, BigDecimalponieważ to wyłapie większość typowych błędów. Jeśli sprawy przebiegają zbyt wolno, muszą dowiedzieć się więcej i znaleźć sposoby na optymalizację swoich problemów. Przedwczesna optymalizacja jest źródłem wszelkiego zła - DE Knuth.
Aaron Digulla
1

Pływaki z natury są nieprecyzyjne i zawsze mają schludne zaokrąglenia. Jeśli precyzja jest ważna, możesz rozważyć refaktoryzację aplikacji tak, aby używała Decimal lub BigDecimal.

Tak, liczby zmiennoprzecinkowe są obliczeniowo szybsze niż liczby dziesiętne ze względu na obsługę procesora. Czy chcesz jednak szybko lub dokładnie?

Nie ja
źródło
1
Arytmetyka dziesiętna też jest niedokładna. (np. 1/3 * 3 == 0.9999999999999999999999999999) Jest to oczywiście lepsze do przedstawiania dokładnych wartości dziesiętnych, takich jak pieniądze, ale w przypadku pomiarów fizycznych nie ma to żadnej korzyści.
dan04
2
Ale 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg
0

Aby uzyskać informacje, można to znaleźć w punkcie 48 - Unikaj liczby zmiennoprzecinkowej i podwajaj, gdy wymagane są dokładne wartości, drugiego wydania Effective Java autorstwa Joshua Blocha. Ta książka jest pełna dobrych rzeczy i zdecydowanie warta obejrzenia.

mR_fr0g
źródło
0

czy to działa?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
źródło
0

Prostym rozwiązaniem, które działa dobrze, jest przeanalizowanie double z ciągu reprezentującego float:

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

Niezbyt wydajne, ale działa!

Alessandro Roaro
źródło