2017-01-12 71 views
12

Błąkałem się, robiąc nieskończone pętle, aby przetestować inny kod/moje zrozumienie, i natknąłem się na dziwne zachowanie. W poniższym programie, liczenie od 0 do 2^24 trwa < 100ms na moim komputerze, ale liczenie do 2^25 zajmuje więcej rzędów wielkości (w momencie pisania, to wciąż jest wykonywane).Dlaczego liczenie do 2^24 wykonuje się szybko, ale liczenie do 2^25 trwa znacznie dłużej?

Dlaczego tak się dzieje?

To było pod Java 1.8.0_101, na kopii 64-bitowego systemu Windows 10.

TestClass.java

public class TestClass { 
    public static void main(String[] args) { 
     addFloats((float) Math.pow(2.0, 24.0)); 
     addFloats((float) Math.pow(2.0, 25.0)); 
    } 

    private static void addFloats(float number) { 
     float f = 0.0f; 
     long startTime = System.currentTimeMillis(); 

     while(true) { 
      f += 1.0f; 
      if (f >= number) { 
       System.out.println(f); 
       System.out.println(number + " took " + (System.currentTimeMillis() - startTime) + " msecs"); 
       break; 
      } 
     } 
    } 
} 
+0

Czy próbowałeś już po prostu uruchamianie addFloats ((float) Math.pow (2.0, 25.0))? –

+5

Ponieważ w pewnym momencie f + 1.0 == f. –

+0

@OlegEstekhin Ahh, to będzie koniec. Jestem idiotą. Dzięki! –

Odpowiedz

16

To dlatego float s mieć minimalną dokładność, które mogą być reprezentowane, które zmniejszyły się wraz ze wzrostem wartości float. Gdzieś pomiędzy 2^24 a 2^25, dodanie jednego nie wystarcza, aby zmienić wartość na następną największą reprezentowalną liczbę. W tym momencie za każdym razem po pętli f utrzymuje tę samą wartość, ponieważ f += 1.0f już jej nie zmienia.

Jeśli zmienisz pętlę do tego:

while(true) { 
    float newF = f + 1.0f; 
    if(newF == f) System.out.println(newF); 
    f += 1.0f; 
    if (f >= number) { 
     System.out.println(f); 
     System.out.println(number + " took " + (System.currentTimeMillis() - startTime) + " msecs"); 
     break; 
    } 
} 

Można zobaczyć takiej sytuacji. Wygląda na to, że przestaje wzrastać, gdy tylko f osiągnie 2^24.

Dane wyjściowe powyższego kodu będą nieskończoną liczbą "1.6777216E7", jeśli uruchomisz go z 2^25.

Możesz przetestować tę wartość, korzystając z Math.nextAfter function, która informuje o następnej możliwej do przedstawienia wartości. Jeśli spróbować uruchomić ten kod:

float value = (float)Math.pow(2.0, 24.0); 
System.out.println(Math.nextAfter(value, Float.MAX_VALUE) - value); 

widać, że następna wartość reprezentowalna po 2^24 jest 2^24 + 2.

Dla doskonałej załamania dlaczego tak się dzieje i dlaczego zaczyna znaczenia, gdzie to robi, zobacz this answer

+5

Innymi słowy, drugi nie zajmuje tylko więcej czasu - to nieskończona pętla! – yshavit

+1

Gdy jesteś w domenie od 2^24 do 2^25, gdzie reprezentowalne 'float's są dokładnie parzystymi liczbami całkowitymi, gdy wykonasz' f + = 1.0f; ', dokładny wynik matematyczny będzie nieparzystą liczbą całkowitą. Ponieważ jest to dokładnie w połowie odległości między dwiema najbliższymi reprezentowalnymi liczbami, większość implementacji wybierze [podwójnie równy] (https://en.wikipedia.org/wiki/Singly_and_doubly_even) jeden (tj. Jeden podzielny przez cztery). W takim przypadku '16777216.0f + 1.0f' będzie miało wartość' 16777216.0f' (bez przyrostu), natomiast '16777218.0f + 1.0f' będzie równe' 16777220.0f' (rzeczywisty przyrost wynosi +2). –

Powiązane problemy