Jak się dowiedzieć, czy BigDecimal może dokładnie przekonwertować na zmiennoprzecinkowe lub podwójne?

10

Klasa BigDecimalma kilka użytecznych metod gwarantujących bezstratną konwersję:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

Jednak metody floatValueExact()i doubleValueExact()nie istnieją.

Czytam kod źródłowy OpenJDK dla metod floatValue()i doubleValue(). Oba wydają się wracać odpowiednio do Float.parseFloat()i Double.parseDouble(), co może zwrócić dodatnią lub ujemną nieskończoność. Na przykład parsowanie ciągu 10 000 cyfr 9 zwróci dodatnią nieskończoność. Jak rozumiem, BigDecimalnie ma wewnętrznej koncepcji nieskończoności. Ponadto parsowanie ciągu 100 9s doubledaje 1.0E100, co nie jest nieskończonością, ale traci precyzję.

Co to jest rozsądne wdrożenie floatValueExact()i doubleValueExact()?

Myślałem o doublerozwiązanie łącząc BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)i Double.toString(double), ale wygląda niechlujnie. Chcę zapytać tutaj, ponieważ może (musi!) Być prostsze rozwiązanie.

Żeby było jasne, nie potrzebuję rozwiązania o wysokiej wydajności.

kevinarpe
źródło
7
Myślę, że możesz przekształcić podwójny, a następnie przekształcić podwójny z powrotem w BigDecimal i sprawdzić, czy uzyskasz tę samą wartość. Nie jestem jednak pewien, jaki jest przypadek użycia. Jeśli używasz podwójnych, już akceptujesz nieprecyzyjne wartości. Jeśli chcesz uzyskać dokładne wartości, pozostań przy BigDecimal.
JB Nizet,

Odpowiedzi:

6

Od czytania na dokumenty , wszystko robi z numTypeValueExactwariantów jest do sprawdzenia istnienia części frakcji lub jeśli wartość jest zbyt duża dla typu liczbowego i rzucać wyjątki.

Jeśli chodzi o floatValue()i doubleValue(), przeprowadzana jest podobna kontrola przepełnienia, ale zamiast zgłosić wyjątek, zamiast tego zwraca Double.POSITIVE_INFINITYlub Double.NEGATIVE_INFINITYdla podwójnych i / Float.POSITIVE_INFINITYlub Float.NEGATIVE_INFINITYzmiennoprzecinkowych.

Dlatego najbardziej rozsądna (i najprostsza) implementacja exactmetod dla liczb zmiennoprzecinkowych i podwójnych powinna po prostu sprawdzić, czy konwersja zwraca POSITIVE_INFINITYlub NEGATIVE_INFINITY.


Ponadto pamiętaj, że BigDecimalzostał zaprojektowany, aby poradzić sobie z brakiem precyzji wynikającym z używania floatlub doubledla dużych irracjonalnych, dlatego też, jak skomentował @JB Nizet , kolejnym sprawdzeniem, które możesz dodać do powyższego, byłoby przekonwertowanie lub powrót, aby sprawdzić, czy nadal otrzymujesz ta sama wartość. To powinno udowodnić, że konwersja była prawidłowa.doublefloatBigDecimal

Oto, jak wyglądałaby taka metoda floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

Zastosowanie compareTozamiast equalspowyższego jest celowe, aby nie stać się zbyt surowym przy kontroli. equalsoceni prawdę tylko wtedy, gdy dwa BigDecimalobiekty mają tę samą wartość i skalę (rozmiar części ułamkowej części dziesiętnej), natomiast compareToprzeoczy tę różnicę, gdy nie ma to znaczenia. Na przykład 2.0vs 2.00.

smac89
źródło
Bardzo przebiegły. Dokładnie to, czego potrzebowałem! Uwaga: Możesz także użyć Float.isFinite()lub Float.isInfinite(), ale jest to opcjonalne. :)
kevinarpe
3
Powinieneś także preferować porównajTo zamiast równych, ponieważ equals () w BigDecimal będzie traktował 2.0 i 2.00 jako różne wartości.
JB Nizet,
Wartości, których nie można dokładnie przedstawić jako zmiennoprzecinkowe, nie będą działać. Przykład: 123.456f. Wydaje mi się, że wynika to z różnej wielkości znaczenia (mantysy) pomiędzy 32-bitowym float i 64-bitowym double. W swojej powyższym kodem, mam lepsze wyniki z: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. Czy to rozsądna zmiana ... czy brakuje mi innego przypadku narożnego wartości zmiennoprzecinkowych?
kevinarpe
1
@kevinarpe - 123.456fjest koncepcyjnie taki sam jak twój przykład 1.0E100. Uderza mnie, że jeśli musisz sprawdzić, czy konwersja z BigDecimal -> binarny zmiennoprzecinkowy jest dokładna, to taka potrzeba jest problemem; tzn. konwersja nie powinna być rozważana.
Stephen C,