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?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
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.
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.
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.
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
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.)
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.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
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));
}
}
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
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.
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ę.
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