2013-04-08 10 views
5

Po raz pierwszy tutaj, mam nadzieję, że to pytanie jest do przyjęcia.Różne odpowiedzi podczas obliczania silni za pomocą iteracji i rekurencji

Jako mały test napisałem aplikację, która oblicza silnię liczby używając zarówno iteracji, jak i rekursji. To wydawało się działać dobrze z wyjątkiem, gdy próbuje obliczyć silnię dla numerów większy niż 24.

Na przykład podczas obliczania silni 24 obie metody dają właściwą odpowiedź 62044840173323941.

Podczas obliczania silni 25 jednak odpowiedzi różnią się. Metoda rekurencyjna podaje odpowiedź jako 1,5511210043330986e + 025, podczas gdy metoda iteracyjna daje odpowiedź jako 1,5511210043330984e + 025.

Zgodnie z Wolfram Alpha poprawna odpowiedź powinna być taka sama, jak metoda iteracyjna, więc dlaczego rozbieżności między funkcjami? Poprosiłem moich kolegów i oni również nie są w stanie wyjaśnić tego zachowania.

#define TEST_CASE 25 

double GetFactorialRecursive(double i) 
{ 
    if (i == 1) 
    return i; 
    else 
    return i * GetFactorialRecursive(i - 1); 
} 

double GetFactorialIterative(double i) 
{ 
    double result = 1.0; 
    for (; i > 0; --i) 
     result *= i; 
    return result; 
} 

int main() 
{ 
double recres = 0, itrres = 0; 
recres = GetFactorialRecursive(TEST_CASE); 
itrres = GetFactorialIterative(TEST_CASE); 

if (recres != itrres) 
    std::cout << "Error" << "\n"; 
std::cout << std::setprecision(25) << "Recursion: " << recres << ", Iteration: " << itrres << "\n"; 
return 0; 

}

Dziękuję za uwagę.

+3

Próbowałem tego sam. Ten sam wynik, również zauważalny, że te dwie liczby różnią się tylko o jeden bit: 'Rekursja: 15511210043330986055303168 [4529a940c33f6121]' i 'Iteracja: 15511210043330983907819520 [4529a940c33f6120]' – benzado

Odpowiedz

9

Wersja rekurencyjne oblicza 5 * (4 * (3 * (2 * 1)))

Wersja iteracyjnego obliczania 1 * (2 * (3 * (4 * 5)))

Różnica w kolejności operacji zmienia sposób arytmetyki ruchem zmiennoprzecinkowym, powodując inny wynik.

+2

Powód, dla którego zmiennoprzecinkowe zaokrągla w tym przypadku, jest taki, że typ "podwójny" może pomieścić tylko około 15-17 cyfr znaczących i 'n!'mają zwykle więcej niż 15-17 cyfr, nawet dla względnie małych wartości' n'. Z drugiej strony Wolfram | Alpha używa zupełnie innej metody niż "podwójny" przy manipulowaniu takimi ilościami. –

2

Kolejność mnożenia jest różna, dając różne wyniki z powodu zaokrąglania zmiennoprzecinkowego.

Jeżeli zmienisz pętlę for jechać z 1 do i (zamiast od i do 1), należy uzyskać taki sam wynik jak z wersji rekurencyjnej.

+0

Jak zauważył Drew Dormann, tak naprawdę możesz nie, chyba że rejestracja pamięci <-> jest dokładnie taka sama po generowaniu kodu, ponieważ rejestry mają tendencję (na architekturze x86) do lepszej precyzji ... –

4

Typ double to not an exact type. Zapowiada się przybliżenie prawidłowej wartości.

Tak więc obie implementacje nie są gwarantowane.

W zależności od implementacji istnieją dwa czynniki, które mogą powodować różne odpowiedzi .

  1. zamawiasz mnożenie w inny sposób.
  2. Twoja wersja iteracyjna wykonuje całą matematykę w tej samej zmiennej. Architektury zgodne z architekturą Intel (x86 i x86-64) używają 80 bitów dokładności w swoich rejestrach zmiennoprzecinkowych, a dokładność ta jest zachowywana, dopóki rejestr nie zostanie zapisany w pamięci.
+0

+1 za wskazanie problemu z rejestrem , to PITA, że optymalizacja (utrzymywanie zmiennej w rejestrze zamiast wrzucania jej z powrotem do pamięci) może faktycznie zmienić obserwowalne zachowanie programu :( –

Powiązane problemy