2011-08-10 14 views
5

Dla następującego kodu (Java):Jak konwersja z double do int działa w Javie?

double d = (double) m/n; //m and n are integers, n>0 
int i = (int) (d * n); 

i == m 

Czy ostatnie wyrażenie zawsze prawdziwe? Jeżeli nie to jest to zawsze prawdziwe ?:

i = (int) Math.round(d * n); 

i == m 
+1

Nie głupie pytanie; pytanie rodzi pewne subtelne kwestie dotyczące arytmetyki zmiennoprzecinkowej i "odzyskiwalności" liczb całkowitych. – Nayuki

+0

Wiele pytań zmiennoprzecinkowych na tej stronie ostatnio - hmm ... –

Odpowiedz

4

Drugie pytanie, które zadajesz, dotyczy rozmiaru ulp w Javie.

Jeśli wartość parametru ulp przekracza 1/(n), zaokrąglenie w mnożeniu nie spowoduje odzyskania pierwotnej podzielonej wartości int. Zazwyczaj większe uniesienia są powiązane z większymi podwójnymi wartościami. Zęby powiązane z podwójnym zaczynają przekraczać 1 około 9E15; jeśli twoje odzyskane debile były w pobliżu, możesz znaleźć problemy z round() nie otrzymaniem oczekiwanej odpowiedzi. Ponieważ pracujesz z wartościami int, największą wartością licznika twojego podziału będzie Integer.MAX_VALUE.

następujących testów programowych wszystkie pozytywne wartości całkowite z n aby zobaczyć, który powoduje, że największy potencjał do zaokrąglania błąd podczas próby odzyskania podzielony int:

public static void main(String[] args) 
    { 
    // start with large number 
    int m = Integer.MAX_VALUE; 
    double d = 0; 

    double largestError = 0; 
    int bigErrorCause = -1; 
    for (int n = 1; n < Integer.MAX_VALUE; n++) 
    { 
     d = (double) m/n; 
     double possibleError = Math.ulp(d) * n; 
     if (possibleError > largestError) 
     { 
     largestError = possibleError; 
     bigErrorCause = n; 
     } 
    } 
    System.out.println("int " + bigErrorCause + " causes at most " 
     + largestError + " error"); 
    } 

Wyjście:

int 1073741823 powoduje nie więcej niż 4.768371577590358E-7 error

Zaokrąglanie przy użyciu Math.round, a następnie rzutowanie na int powinno odzyskać pierwotną int.

1

Matematycznie powinno to być prawdą. Prawdopodobnie jednak otrzymasz błędy zaokrąglania punktów zmiennoprzecinkowych, które spowodują, że będzie on fałszywy. Powinieneś prawie nigdy nie porównywać liczb precyzji zmiennoprzecinkowej przy użyciu ==.

Jesteś znacznie lepiej porównując je za próg takiego:

Math.abs(d*n - m) < 0.000001; 

Zauważ, że oba oświadczenia powinny być równoważne

i = (int) (d * n); 
i = (int) Math.round(d * n); 

Jednak na przykład, jeśli d=3/2 i n=2, pływające błędy punktu mogą skutkować wartością i=2.999999999999, która po obcięciu/zaokrągleniu wynosi 2.

+3

Twoje rozumowanie o skróceniu jest dobre. Ale twój przykład jest zły, ponieważ dzielenie zmiennoprzecinkowe przez 2 jest zawsze dokładne (z wyjątkiem niedomiaru). ;-) – Nayuki

1

Pierwszą z nich jest d nie zawsze to prawda. Drugi powiedziałbym tak, to prawda, ale tylko dlatego, że nie mogę wymyślić kontrprzykładu.

Jeśli n jest bardzo duże, może być fałszywe, nie jestem pewien. Wiem, że to będzie prawda przynajmniej w 99% przypadków.

5

int i = (int) (d * n); i == m;

to fałszywe dla m = 1, n = 49.

i = (int) Math.round(d * n); i == m;

Moja intuicja mówi mi, powinno być prawdziwe, ale może to być trudne do udowodnienia rygorystycznie.

+0

Możesz zweryfikować moje oświadczenie nawet w JavaScript. Możesz wpisać to w przeglądarce: 'javascript: 1/49 * 49', co daje 0.9999 .... – Nayuki

+2

+1 Ponieważ 'double' ma 53-bitową dokładność, a dzielisz i mnożysz przez mniej niż' 2^31' wynik powinien być wyłączony o mniej niż '1/2^21 = 2 * 1/2^22' (współczynnik 2 wynika z wykonania dwóch operacji). Tak więc zaokrąglanie będzie do dokładnej liczby całkowitej przez szeroki margines. – starblue

+0

Usunąłem mój post, ponieważ uważam, że nie miałem racji co do drugiego przypadku, Nicea odpowiedź (już dawałem ci +1) – MByD