Kurs Java Podstawy - rozszerzony

JavaTraps 004 - odpowiedź

Przypomnijmy kod, którego dotyczy zadanie:

public class JavaTraps004 {
	public static void main(String args[]) {
		double x = 2.0;
		double y = 1.1;
		System.out.println(x-y);
	}
}

Wydaje się proste i oczywiste, a oczekiwaną odpowiedzią jest 0.9, jednak o dziwo na ekranie pojawia się odpowiedź D) 0.8999999999999999. Ktoś kto nie zagłębiał się nigdy w konwersję liczb z systemu dziesiętnego na dwójkowy - do obojętnie jakiej formy, może być zaskoczony, dla pozostałych powinno być to dosyć zrozumiałe.

Liczby zmiennoprzecinkowe są reprezentowane w skrócie w taki sposób(nie zagłębiając się w kwestię kodowania):

2m+...+22+21+20.(2-1)+(2-2)+(2-3)+...+(2-n)

Przyglądając się bliżej części ułamkowej widać, że nie zawsze da się uzyskać dokładną oczekiwaną wartość (jak np 0.5, czy 0.25), w takim wypadku otrzymujemy jedynie aproksymację (przybliżenie) danej wartości.

W takim wypadku oczywiste jest, że np w przypadku takiego działania:

		double x = 2.0;
		double y = 1.5;
		System.out.println(x-y);

Otrzymamy oczekiwaną i prawidłową wartość 0.5, czyli 2-2

Jak sobie poradzić z tym problemem?

Oczywiście można sprytnie zamienić metodę print/println na printf z odpowiednio przygotowanym wzorcem:

		double x = 2.0;
		double y = 1.1;
		System.out.printf("%.2f%n", x - y);

Otrzymamy w takim wypadku wartość 0.9, jednak nadal nie zmienia się tutaj kwestia reprezentacji liczby - nadal jest to przybliżenie wartości double.

Prawidłowym rozwiązaniem (szczególnie we wszelkich systemach obliczających dokładne wartości, gdy operujemy na pieniądzach) jest używanie klasy BigDecimal. To co należy dodatkowo zapamiętać, to używanie w przypadku typów BigDecimal konstruktorów przyjmujących typ String, otrzymujemy wtedy dokładnie taką wartość o jaką nam chodzi.

Komentarze

Leszek

Witam. Używam języka Fortran do obliczeń. Ale uczę się JAVY - chciałbym się na nią "przesiąść" a raczej korzystać z jej dobrodziejstwa ewentualnie połączyć obydwa programy. Ale pytanie jest taki - wykonałem w Fortranie (wiadomo język używany głównie do obliczeń) i ten przykład i wynik jest 0.9 - o co tu chodzi czy JAVA "gorzej liczy" albo czy Fortran przewidział "powyższe" i zawiera mechanizmy, które podają "dobry" wynik .

Leszek

Przepraszam - wynik jest 0.8999998 - stało się tak gdy kazałem wyświetlić " więcej miejsc po przecinku. Wcześniej po prostu z automatu Fortran zaokrąglił wynik - czyli zrobił to co na końcu w tym przykładzie - pozdrawiam Leszek

Czarownica

Strona jest kapitalna :)

Mam prośbę: Mógłbyś dodać szerszy opis możliwości metody printf?

Czarownica

Oraz sposób czytania API?

Qbisiek

Chyba czegoś nie rozumiem, dlaczego raz piszesz, że nie da się zapisać wartości 0.5, binarnie, a raz że się da? Ponadto podajesz, że 0.5 = 2^-2, przecież 0.5 = 1/2 = 2^-1, natomiast 2^-2 = 1/4 = 0.25 - kolejna wartość którą trzeba niby aproksymować.

Qbisiek

PS. Ogólnie super stronka :)

Mirek

Wydaje mi się że zdanie zostało niefortunnie sformułowane i chodziło Autorowi o to że nie zawsze da się uzyskać oczekiwaną wartość taką jak 0,5 (w domyśle którą da się otrzymać).
Natomiast rzeczywiście 0,5 to nie jest 2^-2 ale 2^-1 i tu już jest błąd w artykule.