Jak uniknąć błędów precyzji zmiennoprzecinkowej za pomocą float lub doubles w Javie?

Mam bardzo irytujący problem z długimi sumami floatów lub Dubli w Javie. Zasadniczo chodzi o to, że jeśli wykonam:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

To co dostaję to:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

Rozumiem, że istnieje nagromadzenie pływającego błędu precyzji, jednak jak się tego pozbyć? Próbowałem użyć podwaja do połowy błędu, ale wynik jest nadal taki sam.

Jakieś pomysły?
Author: TylerH, 2011-03-10

12 answers

Nie ma dokładnej reprezentacji 0,1 jako float lub double. Z powodu tego błędu reprezentacji wyniki nieznacznie różnią się od oczekiwanych.

Kilka podejść, których możesz użyć:

  • podczas używania typu double Wyświetlaj tylko tyle cyfr, ile potrzebujesz. Podczas sprawdzania równości pozwalają na małą tolerancję w obu kierunkach.
  • alternatywnie użyj typu, który pozwala na przechowywanie liczb, które próbujesz dokładnie reprezentować, na przykład BigDecimal może reprezentować dokładnie 0.1.

Przykładowy kod dla BigDecimal:

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

Zobacz w Internecie: ideone

 32
Author: Mark Byers,
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
2011-03-10 08:46:42

Możesz uniknąć tego konkretnego problemu używając klas takich jak BigDecimal. float i double, będące ZMIENNOPRZECINKOWYMI IEEE 754, nie są zaprojektowane tak, aby były idealnie dokładne, są zaprojektowane tak, aby były szybkie. Ale zwróć uwagę na punkt Jona poniżej: BigDecimal nie może reprezentować "jednej trzeciej" dokładnie, tak samo jak {[2] } może reprezentować" jedną dziesiątą " dokładnie. Ale dla (powiedzmy) obliczeń finansowych, BigDecimal I klasy jak to zwykle jest droga do zrobienia, ponieważ mogą reprezentować liczby w sposób, w jaki ludzie mają tendencję do myślenia o nich.

 9
Author: T.J. Crowder,
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
2011-03-10 08:44:19

Nie używaj float/double w iteratorze, ponieważ maksymalizuje to błąd zaokrąglania. Jeśli używasz tylko następujących

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

Drukuje

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Wiem, że BigDecimal jest popularnym wyborem, ale wolę Podwójne nie dlatego, że jest znacznie szybsze, ale zwykle znacznie krótsze/czystsze do zrozumienia.

Jeśli policzysz liczbę symboli jako miarę złożoności kodu

  • użycie podwójnego = > 11 symboli
  • użyj BigDecimal (z przykładu @Mark Byers) = > 21 symbole

BTW: nie używaj float, chyba że istnieje naprawdę dobry powód, aby nie używać double.

 7
Author: Peter Lawrey,
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
2011-03-10 09:20:11

To nie tylko nagromadzony błąd (i nie ma absolutnie nic wspólnego z Javą). 1.0f, po przetłumaczeniu na rzeczywisty kod, nie ma wartości 0.1 - dostajesz już błąd zaokrąglania.

Z Przewodnik Zmiennoprzecinkowy:

Co mogę zrobić, aby uniknąć tego problemu?

To zależy jakiego rodzaju obliczenia, które robisz.

  • jeśli naprawdę potrzebujesz, aby wyniki dokładnie się sumowały, zwłaszcza gdy pracujesz z pieniędzmi: użyj specjalny dziesiętny typ danych.
  • Jeśli po prostu nie chcesz widzieć tych wszystkich dodatkowych miejsc po przecinku: po prostu sformatuj wynik zaokrąglony do stałej liczba miejsc po przecinku, gdy wyświetlanie go.
  • jeśli nie masz dostępnych danych dziesiętnych, alternatywą jest praca z liczbami całkowitymi, np. czy pieniądze obliczenia w centach. Ale to jest więcej pracy i ma trochę wady.

Przeczytaj stronę linked-to, aby uzyskać szczegółowe informacje.

 3
Author: Michael Borgwardt,
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
2011-03-10 08:40:59

Ze względu na kompletność polecam ten:

Shewchuck, "solidne Adaptacyjne predykaty geometryczne zmiennoprzecinkowe", jeśli chcesz więcej przykładów, jak wykonać dokładną arytmetykę z zmiennoprzecinkową-lub przynajmniej kontrolowaną dokładnością, która jest pierwotną intencją autora, http://www.cs.berkeley.edu/ ~ jrs / papers / robustr. pdf

 3
Author: aka.nice,
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-11-16 20:28:33

Innym rozwiązaniem jest rezygnacja == i sprawdzenie, czy obie wartości są wystarczająco blisko . (Wiem, że nie o to pytałeś w ciele, ale odpowiadam na tytuł pytania.)

 2
Author: aib,
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
2011-03-10 08:45:59

Miałem do czynienia z tym samym problemem, rozwiązałem to samo za pomocą BigDecimal. Poniżej fragment, który mi pomógł.

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);
Mam nadzieję, że ci pomoże.
 2
Author: Balu SKT,
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-07-24 06:39:23

Powinieneś używać dziesiętnego typu danych, a nie floats:

Https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

 2
Author: Tobias Schittkowski,
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-09-08 08:59:16
package loopinamdar;

import java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}
 2
Author: Solus,
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-02-24 09:04:04

Najpierw zrób to podwójne . Nigdy nie używaj float albo będziesz miał problemy z używaniem narzędzi java.lang.Math.

Teraz, jeśli zdarzy ci się znać z góry precyzję, którą chcesz i jest równa lub mniejsza niż 15, wtedy łatwo będzie powiedzieć Twoim podwójnym, aby się zachowywali. Sprawdź poniżej:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

Teraz, gdy wykonujesz operację, musisz podać swój podwójny wynik, aby się zachował:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

Wyjście:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999
Jeśli potrzebujesz więcej niż 15 precyzja to masz pecha:
for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

Wyjście:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

NOTE1: dla wydajności należy buforować operację Math.pow w tablicy. Nie skończyłem tu dla jasności.

NOTE2: dlatego nigdy nie używamy doubleS dla cen, ale longS, gdzie ostatnie N (tzn. gdzie n

 1
Author: rdalmeida,
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-04-14 14:55:16

Jeśli chcesz nadal używać float i uniknąć gromadzenia się błędów przez wielokrotne dodawanie 0.1f, Spróbuj czegoś takiego:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

Zauważ jednak, jak inni już wyjaśnili, że float nie jest nieskończenie precyzyjnym typem danych.

 0
Author: Jesper,
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
2011-03-10 09:18:35

Wystarczy, że zdasz sobie sprawę z precyzji wymaganej w obliczeniach i precyzji, jaką może wykonać wybrany typ danych, i odpowiednio przedstawisz swoje odpowiedzi.

Na przykład, jeśli mamy do czynienia z liczbami z 3 cyframi znaczącymi, należy użyć float (który zapewnia precyzję 7 cyfr znaczących). Jednak nie możesz podać ostatecznej odpowiedzi na precyzję 7 znaczących liczb, jeśli twoje wartości początkowe mają tylko precyzję 2 znaczących jasne.

5.01 + 4.02 = 9.03 (to 3 significant figures)

W twoim przykładzie wykonujesz wiele uzupełnień, a przy każdym dodaniu masz konsekwentny wpływ na ostateczną precyzję.

 0
Author: Nick Pierpoint,
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
2011-03-10 10:10:27