Pytanie Konwertuj float na podwójne bez utraty precyzji


Mam prymitywny float i potrzebuję go jako prymitywne podwójne. Samo rzucenie pływaka do podwójności daje mi niesamowitą 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

Jednakże, jeśli zamiast rzutowania, wyprowadzam float jako ciąg i parsuję ciąg jako podwójne, otrzymuję to, co chcę:

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

Czy jest lepszy sposób niż na String i powrót?


76
2018-05-27 14:39


pochodzenie




Odpowiedzi:


To nie tak, że jesteś tak właściwie uzyskanie dodatkowej precyzji - chodzi o to, że kursor nie odzwierciedlał dokładnie numeru, na który początkowo celowałeś. Podwójny jest reprezentowanie oryginalnego pływaka dokładnie; toString pokazuje "dodatkowe" dane, które były już obecne.

Na przykład (i te liczby są nieprawidłowe, po prostu wymyślam) wsparcie, które miałeś:

float f = 0.1F;
double d = f;

Następnie wartość f może być dokładnie 0.100000234523. d będzie miał dokładnie taką samą wartość, ale po przekonwertowaniu go na ciąg będzie "ufał", że jest dokładniejszy z większą precyzją, więc nie zakończy się tak wcześnie, a zobaczysz "dodatkowe cyfry", które były już tam, ale ukryty przed tobą.

Kiedy konwertujesz na ciąg znaków i powracasz, uzyskujesz podwójną wartość, która jest bliższa wartości ciągu niż oryginalna zmienna - ale to tylko dobre gdyby naprawdę wierzysz, że wartość ciągu jest tym, czego naprawdę chcesz.

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


103
2018-05-27 14:42



Myślę, że twoja odpowiedź najlepiej opisuje przyczynę mojego problemu. Moje rozwiązanie może "działać", ale zdaję sobie sprawę, że teraz zadaję niewłaściwe pytanie. Dzięki. - Steve Armstrong
Mam ten sam problem w .NET i to wyjaśnia bardzo dobrze, dziękuję. - EMP


Uważam, że konwersja do binarnej reprezentacji łatwiej jest uchwycić ten problem.

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 liczba zmiennoprzecinkowa jest rozwinięta do podwójnej przez dodanie 0 do końca, ale podwójna reprezentacja 0,27 jest "dokładniejsza", stąd problem.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

32
2018-02-07 08:45





Wynika to z umowy z Float.toString(float), co częściowo mówi:

Ile cyfr musi być wydrukowanych   część ułamkowa [...]? Tam   musi wynosić co najmniej jedną cyfrę   reprezentują część ułamkową, oraz   poza tym jak wiele, ale tylko tyle, więcej cyfr, które są potrzebne do wyjątkowego   odróżnić wartość argumentu od   sąsiednie wartości typu float. Że   jest, załóżmy, że x jest dokładny   wartość matematyczna reprezentowana przez   dziesiętna reprezentacja wyprodukowana przez   ta metoda dla skończonego niezerowego   argument f. Wtedy f musi być floatem   wartość najbliższa x; lub, jeśli dwa pływak   wartości są równie bliskie x, a następnie f   musi być jednym z nich i najmniej   znaczący kawałek znaczenia   f musi być 0.


23
2018-05-27 14:56



+1 Świetna odpowiedź z cytatem z dokumentacji. - EMP


Znalazłem ten problem już dziś i nie mogłem użyć refaktora w 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.

Należy zauważyć, ż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 jest to poprawne rozwiązanie.


14
2018-01-02 12:24



Pracował dla mnie. Jest również bardzo szybki. Nie wiem, dlaczego nie jest to zaznaczone jako odpowiedź. - Sameer
Jest to metoda, której używam, a ty nie potrzebujesz nowej części Float ... Po prostu utwórz plik FloatingDecimal z prymitywem. To będzie automatycznie zapakowane w pudełku ... I działa też szybko ... Jest jedna, wada, FloatingDecimal jest niezmienna, więc musisz stworzyć jedną dla każdego float ... Wyobraź sobie kod obliczeniowy O (1e10)! !! Oczywiście BigDecimal ma tę samą wadę ... - Mostafa Zeinali
Wadą tego rozwiązania jest to, że sun.misc.FloatingDecimal jest wewnętrzną klasą JVM, a sygnatura jego konstruktora została zmieniona w Javie 1.8. Nikt nie powinien korzystać z klas wewnętrznych w prawdziwym życiu. - Igor Bljahhin


Użyć BigDecimalzamiast float/double. Istnieje wiele liczb, których nie można przedstawić jako binarnego zmiennoprzecinkowego (na przykład 0.1). Musisz więc zawsze zaokrąglić wynik do znanej precyzji lub użycia BigDecimal.

Widzieć http://en.wikipedia.org/wiki/Floating_point po więcej informacji.


6
2018-05-27 14:45



Powiedzmy, że w pamięci podręcznej masz 10 mln cen transakcyjnych, czy nadal używasz BigDecimal i tworzysz instancję unikatową, powiedzmy ~ 6m obiektów (by obniżyć flyweight / immutable) ... czy jest w tym coś więcej? - Sendi_t
@Sendi_t Prosimy nie używać komentarzy do zadawania złożonych pytań :-) - Aaron Digulla
Dzięki za patrzenie! ... używamy teraz pływaków, jak to ustalono dekadę temu - Próbowałem zobaczyć twój punkt widzenia na tę sytuację w następstwie -. dzięki! - Sendi_t
@Sendi_t Nieporozumienie. Nie mogę odpowiedzieć na twoje pytanie w 512 znakach. Zadaj właściwe pytanie i wyślij mi link. - Aaron Digulla
@GKFX Nie ma "jednego rozmiaru dla wszystkich", jeśli chodzi o obsługę dziesiętnych na komputerach. Ale ponieważ większość ludzi tego nie rozumie, ja ich wskazuję BigDecimal ponieważ spowoduje to wychwycenie większości typowych błędów. Jeśli sprawy są zbyt powolne, muszą nauczyć się więcej i znaleźć sposoby na zoptymalizowanie swoich problemów. Przedwczesna optymalizacja jest źródłem wszelkiego zła - D.E. Knuth. - Aaron Digulla


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

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

Jeśli użyjesz pływak i podwójniezamiast Pływak i Podwójnie użyj:

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

4
2018-01-25 16:20





Pływaki z natury są nieprecyzyjne i zawsze mają zaokrąglone "problemy". Jeśli dokładność jest ważna, możesz rozważyć refaktoryzację swojej aplikacji, aby użyła Decimal lub BigDecimal.

Tak, zmienne są obliczeniowo szybsze niż wartości dziesiętne ze względu na obsługę procesorów. Jednak chcesz szybko lub dokładnie?


1
2018-05-27 14:55



Dziesiętna arytmetyka również jest niedokładna. (np. 1/3 * 3 == 0,9999999999999999999999999999) Jest oczywiście lepiej reprezentować dokładną dziesiętny takie jak pieniądze, ale w przypadku pomiarów fizycznych nie ma żadnej korzyści. - dan04
Ale 1 == 0,9999999999999999999999999999 :) - Emmanuel Bourg


Aby uzyskać informacje, jest to pod pozycją 48 - Unikaj float i podwójne, gdy wymagane są dokładne wartości, w Effective Java 2nd edition autorstwa Joshua Bloch. Ta książka jest pełna dobrych rzeczy i zdecydowanie warta zobaczenia.


0
2018-05-27 15:05