Zachowaj precyzję dzięki podwójnemu w Javie

public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total += 5.6;
        total += 5.8;
        System.out.println(total);
    }
}

Powyższy kod drukuje:

11.399999999999

Jak mam to po prostu wydrukować (lub być w stanie użyć go jako) 11.4?

Author: Makoto, 2008-11-27

20 answers

Jak już wspomnieli inni, prawdopodobnie będziesz chciał użyć BigDecimal klasa, jeśli chcesz mieć dokładną reprezentację 11.4.

Teraz małe wyjaśnienie, dlaczego tak się dzieje:

Prymitywne typy w Javie to liczby zmiennoprzecinkowe , gdzie liczba jest przechowywana jako binarna reprezentacja ułamka i wykładnika.

Dokładniej, wartość zmiennoprzecinkowa o podwójnej precyzji, taka jak typ double, jest 64-bitową wartość, gdzie:

  • 1 bit oznacza znak (dodatni lub ujemny).
  • 11 bitów dla wykładnika.
  • 52 bity dla cyfr znaczących (część ułamkowa jako binarna).

Te części są połączone, aby stworzyć double reprezentację wartości.

(Źródło: Wikipedia: Podwójna precyzja )

Szczegółowy opis obsługi wartości zmiennoprzecinkowych w języku Java znajduje się w sekcja 4.2.3: typy zmiennoprzecinkowe, Formaty i wartości specyfikacji języka Java.

The byte, char, int, long typy są liczbami stałopunktowymi , które są dokładnymi reprezentacjami liczb. W przeciwieństwie do liczb stałych, liczby zmiennoprzecinkowe czasami (można założyć, że "większość czasu") nie będą w stanie zwrócić dokładnej reprezentacji liczby. To jest powód, dla którego kończy się 11.399999999999 w wyniku 5.6 + 5.8.

Gdy wymagamy dokładnej wartości, takiej jak 1.5 lub 150.1005, będziesz chciał użyć jednego z typów punktów stałych, który będzie w stanie dokładnie reprezentować liczbę.

Jak już kilkakrotnie wspomniano, Java ma BigDecimal klasa, która obsłuży bardzo duże i bardzo małe liczby.

From the Java API Reference for the BigDecimal class:

Immutable, Dowolna-precyzja podpisana dziesiętna liczby. BigDecimal składa się z Dowolna dokładna liczba całkowita bez szwanku wartość i 32-bitowa liczba całkowita skala. Jeśli zero lub dodatni, skala jest liczba cyfr po prawej stronie punkt dziesiętny. Jeśli ujemne, to bezskalowana wartość liczby to pomnożona przez dziesięć do potęgi negacja skali. Wartość liczba reprezentowana przez BigDecimal jest zatem (unscaledValue × 10^-Skala).

Pojawiło się wiele pytań dotyczących przepełnienia stosu, dotyczących kwestii liczb zmiennoprzecinkowych i ich precyzji. Oto lista powiązanych pytań, które mogą być interesujące:

Jeśli naprawdę chcesz przejść do szczegółów liczb zmiennoprzecinkowych, spójrz na co Każdy Informatyk Powinien Wiedzieć O Arytmetyce Zmiennoprzecinkowej .

 120
Author: coobird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Kiedy wprowadzasz liczbę podwójną, na przykład 33.33333333333333, otrzymana wartość jest w rzeczywistości najbliższą reprezentowalną wartością podwójnej precyzji, która jest dokładnie:

33.3333333333333285963817615993320941925048828125

Dzieląc to przez 100 daje:

0.333333333333333285963817615993320941925048828125

Która również nie jest reprezentowalna jako Liczba o podwójnej precyzji, więc ponownie jest zaokrąglana do najbliższej reprezentowalnej wartości, która jest dokładnie:

0.3333333333333332593184650249895639717578887939453125

Po wydrukowaniu tej wartości, zostanie ona zaokrąglona Jeszcze raz do 17 cyfr dziesiętnych, dając:

0.33333333333333326
 96
Author: Stephen Canon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-28 22:55:56

Jeśli chcesz przetwarzać wartości jako ułamki, możesz utworzyć klasę ułamkową zawierającą pole licznika i mianownika.

Zapis metod dodawania, odejmowania, mnożenia i dzielenia oraz metody toDouble. W ten sposób można uniknąć pływaków podczas obliczeń.

EDIT: szybka realizacja,

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

}
 23
Author: Viral Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-26 17:14:54

Zauważ, że miałbyś ten sam problem, gdybyś użył arytmetyki dziesiętnej o ograniczonej precyzji i chciał poradzić sobie z 1/3: 0,333333333 * 3 to 0,999999999, a nie 1,000000000.

Niestety, 5.6, 5.8 i 11.4 po prostu nie są okrągłymi liczbami binarnymi, ponieważ zawierają piątki. Więc ich reprezentacja float nie jest dokładna, podobnie jak 0,3333 nie jest dokładnie 1/3.

Jeśli wszystkie liczby, których używasz, są jednorazowymi cyframi dziesiętnymi i chcesz uzyskać dokładne wyniki, użyj BigDecimal. Lub jak inni powiedział, Jeśli twoje wartości są jak pieniądze w tym sensie, że wszystkie są wielokrotnością 0.01, lub 0.001, lub coś, następnie pomnożyć wszystko przez stałą moc 10 i użyć int lub long (dodawanie i odejmowanie są trywialne: uważaj na mnożenie).

Jeśli jednak jesteś zadowolony z binarnych obliczeń, ale chcesz tylko wydrukować rzeczy w nieco bardziej przyjaznym formacie, spróbuj java.util.Formatter lub String.format. W formacie string określa dokładność mniejszą od pełnej precyzji podwójnej. Do 10 znaczące liczby, powiedzmy, 11.3999999999999999 jest 11.4, więc wynik będzie prawie tak dokładny i bardziej czytelny dla człowieka w przypadkach, gdy wynik binarny jest bardzo bliski wartości wymagającej tylko kilku miejsc po przecinku.

Precyzja określenia zależy trochę od tego, ile matematyki zrobiłeś ze swoimi liczbami - ogólnie rzecz biorąc, im więcej zrobisz, tym więcej błędów będzie się gromadzić, ale niektóre algorytmy gromadzą je znacznie szybciej niż inne (nazywane są " niestabilne "w przeciwieństwie do "stabilne" w odniesieniu do błędy zaokrąglania). Jeśli wszystko, co robisz, to dodawanie kilku wartości, to domyślam się, że upuszczenie tylko jednego miejsca po przecinku precyzji wszystko uporządkuje. Eksperyment.

 15
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 03:23:08

Możesz zajrzeć do korzystania z Javy.matematyka.Klasa BigDecimal, jeśli naprawdę potrzebujesz precyzyjnej matematyki. Oto dobry artykuł z Oracle / Sun na temat sprawy dla BigDecimal . Chociaż nigdy nie możesz reprezentować 1/3, jak ktoś wspomniał, możesz mieć moc decydowania, jak dokładnie chcesz, aby wynik był precyzyjny. setScale() jest twoim przyjacielem.. :)

Ok, ponieważ mam zbyt dużo czasu na ręce w tej chwili tutaj jest przykład kodu, który odnosi się do twojego pytanie:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * [email protected]
 * 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


    }
}

I aby podłączyć mój nowy ulubiony język, Groovy, oto lepszy przykład tego samego:

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
 8
Author: Vinny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-25 01:18:32

Jak zauważyli inni, nie wszystkie wartości dziesiętne mogą być reprezentowane jako binarne, ponieważ decimal opiera się na potęgach 10, a binarne na potęgach dwóch.

Jeśli precyzja ma znaczenie, użyj BigDecimal, ale jeśli chcesz tylko przyjazne Wyjście:

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

Da ci:

11.40
 6
Author: Draemon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 03:52:24

Całkiem pewny, że można to zrobić w trzylinijkowym przykładzie. :)

Jeśli chcesz dokładnej precyzji, użyj BigDecimal. W przeciwnym razie możesz użyć ints pomnożonego przez 10^, niezależnie od precyzji.

 5
Author: Dustin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 01:57:20

Masz problem z ograniczeniem precyzji typu double.

Java.Matematyka ma pewne arbitralno-precyzyjne funkcje arytmetyczne.

 5
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-18 00:35:13

Nie możesz, ponieważ 7.3 nie ma skończonej reprezentacji w systemie binarnym. Najbliżej można uzyskać 2054767329987789/2 * * 48 = 7.3+1/1407374883553280.

Spójrz na http://docs.python.org/tutorial/floatingpoint.html dla dalszego wyjaśnienia. (Jest na stronie Pythona, ale Java i C++ mają ten sam "problem".)

Rozwiązanie zależy od tego, jaki dokładnie jest Twój problem:

  • Jeśli po prostu nie lubisz widzieć tych wszystkich cyfr szumu, to napraw Twoje formatowanie łańcuchów. Nie wyświetla więcej niż 15 cyfr znaczących (lub 7 dla float).
  • Jeśli chodzi o to, że niedokładność Twoich liczb łamie rzeczy takie jak wyrażenia "if", to powinieneś napisać if (abs (x - 7.3)
  • jeśli pracujesz z pieniędzmi, to prawdopodobnie naprawdę chcesz to dziesiętny punkt stały. Przechowuj całkowitą liczbę centów lub jakąkolwiek najmniejszą jednostkę waluty.
  • (bardzo mało prawdopodobne) , jeśli potrzebujesz więcej niż 53 znaczące bity (15-16 znaczących cyfr) precyzji, a następnie użyć precyzyjnego typu zmiennoprzecinkowego, jak BigDecimal.
 5
Author: dan04,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-18 02:37:52
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
}
 4
Author: sravan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 18:28:16

Użyj Javy.matematyka.BigDecimal

Dwójki są ułamkami binarnymi wewnętrznie, Więc czasami nie mogą reprezentować ułamków dziesiętnych do dokładnego dziesiętnego.

 3
Author: Kevin Crowell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-17 23:15:40

Pomnóż wszystko przez 100 i przechowuj w tak długim czasie jak centy.

 2
Author: Paul Tomblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 01:56:29

Komputery przechowują liczby w postaci binarnej i nie mogą właściwie reprezentować liczb takich jak 33.33333333 lub 100.0. Jest to jedna z trudnych rzeczy związanych z używaniem sobowtórów. Musisz tylko zaokrąglić odpowiedź przed pokazaniem jej użytkownikowi. Na szczęście w większości aplikacji nie potrzebujesz tak wielu miejsc po przecinku.

 2
Author: Jay Askren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-18 00:38:54

Liczby zmiennoprzecinkowe różnią się od liczb rzeczywistych tym, że dla danej liczby zmiennoprzecinkowej istnieje kolejna wyższa liczba zmiennoprzecinkowa. Tak samo jak liczby całkowite. Nie ma liczby całkowitej między 1 A 2.

Nie ma możliwości reprezentowania 1/3 jako float. Jest pływak pod nim i jest pływak nad nim, i jest pewna odległość między nimi. I 1/3 jest w tej przestrzeni.

Apfloat for Java twierdzi, że działa z dowolnymi precyzyjnymi liczbami zmiennoprzecinkowymi, ale nigdy go nie używałem. Pewnie warto zajrzeć. http://www.apfloat.org/apfloat_java/

Podobne pytanie zadano tu wcześniej Java zmiennoprzecinkowa biblioteka wysokiej precyzji

 2
Author: Spike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:45

Dwójki to przybliżenia liczb dziesiętnych w źródle Javy. Widzisz konsekwencję niedopasowania między podwójnym (który jest wartością kodowaną binarnie) a Twoim źródłem (który jest kodowany dziesiętnie).

Java produkuje najbliższe przybliżenie binarne. Możesz użyć Javy.tekst.DecimalFormat wyświetla lepiej wyglądającą wartość dziesiętną.

 1
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 02:22:49

Użyj BigDecimal. Pozwala nawet określić reguły zaokrąglania (takie jak ROUND_HALF_EVEN, które zminimalizują błąd statystyczny przez zaokrąglenie do parzystego sąsiada, jeśli obie są tej samej odległości; tj. zarówno 1,5, jak i 2,5 rundy do 2).

 1
Author: Adam Jaskiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-27 02:36:26

Zobacz BigDecimal, radzi sobie z problemami z arytmetyką zmiennoprzecinkową.

Nowe połączenie wyglądałoby tak:

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

Użyj metody setScale (), aby ustawić liczbę dokładności miejsca dziesiętnego, która ma być użyta.

 0
Author: Dark Castle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-17 23:14:14

Dlaczego nie użyć metody round() z klasy matematycznej?

// 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
Author: Mr.Cat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-14 16:26:06

Jeśli nie masz innego wyboru niż użycie podwójnych wartości, możesz użyć poniższego kodu.

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;
}
 0
Author: user5734382,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-07 10:59:58

Nie marnuj wysiłku używając BigDecimal. W 99,99999% przypadków nie potrzebujesz go. Typ java double jest co prawda przybliżony, ale w prawie wszystkich przypadkach jest wystarczająco precyzyjny. Pamiętaj, że masz błąd na 14 cyfrze znaczącej. To naprawdę nieistotne!

Aby uzyskać ładne wyjście użyj:

System.out.printf("%.2f\n", total);
 -1
Author: Maciek D.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-21 12:16:14