Jak komputer liczy pierwiastek kwadratowy?

Projekt szkolny poświęcony numerycznym metodom obliczania pierwiastka kwadratowego — od glinianej tabliczki babilońskiej sprzed niemal czterech tysięcy lat, przez matematyczną teorię zbieżności, po realny eksperyment porównujący czas działania własnej implementacji z funkcją biblioteczną sqrt().

\(x_{n+1} = \dfrac{1}{2}\left(x_n + \dfrac{S}{x_n}\right)\)
  • Metoda babilońska (Herona)
  • C++ / Python / Excel
  • 1 000 000 obliczeń × 5 powtórzeń
  • Dokładność 15 miejsc po przecinku
  • 01 Historia

    Pierwiastek kwadratowy liczono na długo przed powstaniem matematyki symbolicznej — metody, które dziś programujemy, mają tysiące lat.

    02 Podstawy matematyczne

    Skąd właściwie bierze się wzór \(\tfrac12(x + S/x)\)? Wyprowadźmy go z ogólnej metody Newtona i sprawdźmy, dlaczego zbiega tak szybko.

    Definicja

    Pierwiastkiem kwadratowym z liczby nieujemnej \(S\) nazywamy liczbę \(x \ge 0\) spełniającą równanie \(x^2 = S\). Zagadnienie obliczeniowe sprowadza się więc do znalezienia miejsca zerowego funkcji:

    \[f(x) = x^2 - S\]

    Wyprowadzenie z metody Newtona

    Ogólny wzór iteracyjny Newtona–Raphsona dla równania \(f(x) = 0\) ma postać:

    \[x_{n+1} = x_n - \dfrac{f(x_n)}{f'(x_n)}\]

    Dla \(f(x) = x^2 - S\) pochodna wynosi \(f'(x) = 2x\), zatem:

    \[ \begin{aligned} x_{n+1} &= x_n - \frac{x_n^2 - S}{2x_n} \\[4pt] &= \frac{2x_n^2 - x_n^2 + S}{2x_n} \\[4pt] &= \frac{x_n^2 + S}{2x_n} \\[4pt] &= \frac{1}{2}\left(x_n + \frac{S}{x_n}\right) \end{aligned} \]

    Metoda babilońska to więc dokładnie metoda Newtona zastosowana do równania \(x^2 = S\) — z tą różnicą, że Babilończycy odkryli ją intuicyjnie, niemal 3000 lat przed Newtonem.

    Interpretacja geometryczna: uśrednianie

    Jeżeli \(x\) jest przybliżeniem \(\sqrt{S}\) z nadmiarem, to \(S/x\) jest przybliżeniem z niedomiarem (i odwrotnie) — bo ich iloczyn zawsze wynosi dokładnie \(S\). Nowe przybliżenie to po prostu średnia arytmetyczna tych dwóch liczb, która zawsze leży bliżej prawdziwego pierwiastka niż gorsze z dwóch wyjściowych przybliżeń. Z nierówności między średnią arytmetyczną a geometryczną (AM–GM) wynika, że dla \(n \ge 1\) zawsze \(x_n \ge \sqrt{S}\), a ciąg \((x_n)\) jest malejący i zbieżny do \(\sqrt{S}\).

    Dlaczego zbiega tak szybko

    Niech \(e_n = x_n - \sqrt{S}\) oznacza błąd po \(n\)-tej iteracji. Podstawiając do wzoru iteracyjnego i upraszczając, otrzymujemy:

    \[e_{n+1} \approx \dfrac{e_n^2}{2x_n}\]

    Błąd w kolejnym kroku jest w przybliżeniu proporcjonalny do kwadratu poprzedniego błędu — to tzw. zbieżność kwadratowa. W praktyce oznacza to, że liczba poprawnych cyfr znaczących podwaja się z każdą iteracją. Poniższa tabela (dla \(S = 10\), \(x_0 = 10\)) pokazuje to zjawisko: już po 6 iteracjach osiągana jest pełna precyzja liczby zmiennoprzecinkowej typu double (ok. 15–16 cyfr znaczących).

    n\(x_n\)\(|x_n - x_{n-1}|\)
    010,000000000000000—
    15,5000000000000004,5 × 10⁰
    23,6590909090909091,8 × 10⁰
    33,1960050818746474,6 × 10⁻¹
    43,1624556228038903,4 × 10⁻²
    53,1622776651756751,8 × 10⁻⁴
    63,1622776601683795,0 × 10⁻⁹
    73,162277660168379≈ 0 (zbieżność)

    03 Metoda babilońska

    Praktyczna strona algorytmu: jak go zatrzymać, ile kosztuje, i jak wybrać przybliżenie startowe.

    Krok po kroku

    1. Wybierz przybliżenie startowe \(x_0 > 0\) (najprościej: \(x_0 = S\)).
    2. Oblicz \(x_{n+1} = \tfrac12\left(x_n + \dfrac{S}{x_n}\right)\).
    3. Jeśli \(|x_{n+1} - x_n| < \varepsilon\), zakończ — wynikiem jest \(x_{n+1}\).
    4. W przeciwnym razie wróć do kroku 2.

    Warunek stopu i dokładność

    W tym projekcie przyjęto \(\varepsilon = 10^{-15}\), czyli dokładność do 15 miejsc po przecinku — w praktyce granica sensownej precyzji liczby typu double (64-bitowej liczby zmiennoprzecinkowej IEEE 754, ok. 15–17 cyfr znaczących). Dzięki zbieżności kwadratowej osiągnięcie tej dokładności wymaga zwykle mniej niż 10 iteracji — nawet dla bardzo dużych liczb.

    Dlaczego \(x_0 = S\) jest bezpiecznym wyborem? Nawet dla dużych \(S\) pierwsza iteracja natychmiast sprowadza przybliżenie w rozsądny zakres (\(x_1 = (S+1)/2\)), a kolejne kroki korzystają już z pełnej zbieżności kwadratowej. Lepszy start (np. przybliżenie z rejestru wykładnika liczby zmiennoprzecinkowej) skraca liczbę iteracji o 1–2, ale nie zmienia rzędu złożoności.

    Wypróbuj sam — kalkulator na żywo

    Wpisz dowolną liczbę nieujemną — poniższa tabela pokaże, krok po kroku, jak metoda babilońska (uruchomiona bezpośrednio w Twojej przeglądarce, w JavaScript) dochodzi do wyniku, i porówna go z wbudowaną funkcją Math.sqrt().

    04 Inne metody (dla chętnych)

    Metoda babilońska nie jest jedynym sposobem — oto krótki przegląd alternatyw, historycznych i współczesnych.

    Metoda bisekcji

    Szukamy miejsca zerowego \(f(x) = x^2 - S\) dzieląc przedział \([0, \max(1, S)]\) na pół i zawężając go w stronę pierwiastka. Prosta i zawsze zbieżna, ale liniowa — każda iteracja daje tylko jeden dodatkowy bit dokładności, więc do 15 cyfr po przecinku potrzeba dziesiątek, a nie kilku iteracji.

    Algorytm „cyfra po cyfrze”

    Klasyczna metoda pisemna znana ze szkoły — pierwiastek wyznacza się cyfra po cyfrze, analogicznie do dzielenia pisemnego, grupując cyfry liczby w pary od przecinka. Używana też w implementacjach sprzętowych (m.in. w starszych FPU) w wariancie binarnym.

    Fast inverse square root

    Słynna sztuczka z Quake III Arena (1999): interpretuje się bity liczby zmiennoprzecinkowej jako liczbę całkowitą, odejmuje ją od magicznej stałej 0x5f3759df, co daje zaskakująco dobre przybliżenie \(1/\sqrt{x}\), a następnie dokonuje jednej iteracji metody Newtona, by je dopracować. Pokazuje, że sama metoda babilońska/Newtona wciąż stoi w centrum nawet najbardziej „sprytnych” hacków.

    CORDIC

    Algorytm typu „przesuń i dodaj” (bez mnożenia i dzielenia), używany w kalkulatorach i układach FPGA. Oblicza pierwiastek (i funkcje trygonometryczne) za pomocą sekwencji przesunięć bitowych i dodawań — wydajny sprzętowo, choć wolniej zbieżny niż metoda Newtona.

    Wszystkie te metody łączy jedno: sprowadzają obliczenie pierwiastka do prostszych operacji (dodawanie, dzielenie, przesunięcia bitowe) wykonywanych iteracyjnie — różnią się jedynie tempem zbieżności i kosztem pojedynczego kroku.

    05 Realizacja metody babilońskiej

    Ten sam algorytm w czterech wcieleniach: pseudokod, C++, Python i arkusz kalkulacyjny.

    Pseudokod

    funkcja pierwiastek_babilonski(S, eps = 1e-15):
        jeśli S < 0:  zwróć błąd
        jeśli S = 0:  zwróć 0
    
        x ← S                      // przybliżenie startowe
        powtarzaj:
            poprzednie ← x
            x ← 0.5 * (x + S / x)  // krok Herona
        dopóki |x - poprzednie| > eps
    
        zwróć x

    C++

    babilonska.cpp
    double pierwiastekBabilonski(double S, double eps = 1e-15) {
        if (S < 0) return NAN;
        if (S == 0) return 0.0;
    
        double x = S;                 // przyblizenie poczatkowe
        double poprzednie;
    
        do {
            poprzednie = x;
            x = 0.5 * (x + S / x);    // wzor iteracyjny Herona
        } while (std::fabs(x - poprzednie) > eps);
    
        return x;
    }

    Przykładowy wynik uruchomienia (pełny kod w code/cpp/babilonska.cpp):

    sqrt(2.000000000000000)
      metoda babilonska : 1.414213562373095
      std::sqrt          : 1.414213562373095
      roznica            : 0.000000000000000
    
    sqrt(999999.000000000000000)
      metoda babilonska : 999.999499999875070
      std::sqrt          : 999.999499999874956
      roznica            : 0.000000000000114

    Python

    babilonska.py
    def pierwiastek_babilonski(s: float, eps: float = 1e-15) -> float:
        if s < 0:
            raise ValueError("Liczba pod pierwiastkiem nie moze byc ujemna")
        if s == 0:
            return 0.0
    
        x = s
        while True:
            poprzednie = x
            x = 0.5 * (x + s / x)
            if abs(x - poprzednie) <= eps:
                return x

    Przykładowy wynik uruchomienia (pełny kod w code/python/babilonska.py):

    sqrt(2)
      metoda babilonska : 1.414213562373095
      math.sqrt          : 1.414213562373095
      roznica            : 0.000000000000000
    
    sqrt(999999)
      metoda babilonska : 999.999499999875070
      math.sqrt          : 999.999499999874956
      roznica            : 0.000000000000114

    Arkusz kalkulacyjny (Excel)

    Plik code/excel/metoda_babilonska.xlsx zawiera dwa arkusze. Pierwszy rozpisuje kolejne iteracje wiersz po wierszu — komórka S jest parametrem wejściowym, a każdy wiersz odwołuje się formułą do wiersza poprzedniego:

    n
    x_n (formuła)
    |różnica|
    0
    =C5 (x₀ = S)
    1
    =0.5*(C9+$C$4/C9)
    =ABS(C10-C9)
    2
    =0.5*(C10+$C$4/C10)
    =ABS(C11-C10)
    …
    …
    …
    24
    =0.5*(C32+$C$4/C32)
    ≈ 0
    Podgląd struktury arkusza „Metoda babilonska” — 24 wiersze iteracji, kolumna różnic wybarwiona skalą kolorów od czerwieni (duża różnica) do zieleni (zbieżność).

    Drugi arkusz, „Porównanie kilku liczb”, zestawia dla kilku wartości S wynik metody babilońskiej (jako jedna, 24-krotnie zagnieżdżona formuła Herona) z wbudowaną funkcją =SQRT(S) oraz różnicę bezwzględną między nimi — obie kolumny liczone są w pełni formułami, bez wpisywania gotowych wyników.

    06 Eksperyment

    Czy „ręczna” metoda babilońska jest w praktyce wolniejsza od funkcji bibliotecznej — i o ile?

    Metodologia

    Dlaczego cała seria, a nie jedno wywołanie? Pojedyncze wywołanie sqrt() trwa ułamki nanosekund — poniżej rozdzielczości zegara systemowego i podatne na szum pomiarowy. Sumowanie czasu 1 000 000 wywołań uśrednia ten szum i daje wynik odtwarzalny między próbami.

    07 Wyniki

    Pięć powtórzeń serii 1 000 000 obliczeń, C++, wynik w sekundach.

    Próbasqrt()Metoda babilońska
    10,004118 s0,047588 s
    20,003889 s0,044553 s
    30,003889 s0,044695 s
    40,003935 s0,044954 s
    50,004006 s0,045020 s
    Średnia0,003967 s0,045362 s
    ~11,4×
    metoda babilońska wolniejsza od sqrt() (C++)
    6–7
    iteracji do zbieżności (typowa liczba, S ≤ 10⁶)
    \(<1{,}2\times10^{-13}\)
    maks. różnica wyniku obu metod w testach
    Wykres słupkowy: czas sqrt() vs metoda babilońska w 5 próbach i średnia
    Rys. 1. Czas wykonania 1 000 000 obliczeń: sqrt() a metoda babilońska, C++, 5 prób + średnia.

    Dodatkowo: to samo w Pythonie

    Dla porównania między językami przeprowadzono identyczny eksperyment w Pythonie (code/python/eksperyment.py) — nieobowiązkowe rozszerzenie, poza wymaganym zakresem zadania, ale dobrze ilustrujące wpływ narzutu interpretera na algorytmy iteracyjne.

    Próbasqrt()Metoda babilońska
    10,086439 s1,621391 s
    20,087567 s1,704114 s
    30,087608 s1,604282 s
    40,088479 s1,615481 s
    50,087932 s1,619986 s
    Średnia0,087605 s1,633051 s
    Wykres: średnie czasy C++ sqrt, C++ babilonska, Python sqrt, Python babilonska w skali logarytmicznej
    Rys. 2. Średnie czasy wszystkich czterech wariantów (skala logarytmiczna) — C++ vs Python, sqrt() vs metoda babilońska.

    08 Wnioski

    1. Metoda babilońska jest poprawna — dla całego przebadanego zakresu (1…1 000 000) wyniki obu metod zgadzają się do 13.–15. miejsca po przecinku; jedyne różnice wynikają z akumulacji błędów zaokrągleń liczb zmiennoprzecinkowych, a nie z błędu algorytmu.
    2. Jest jednak wyraźnie wolniejsza — w C++ około 11–12 razy, a w Pythonie prawie 19 razy wolniejsza od wbudowanej funkcji sqrt(). Różnica ta wynika z tego, że biblioteczny sqrt() korzysta bezpośrednio ze sprzętowej instrukcji procesora (jeden takt zegara), podczas gdy metoda babilońska wykonuje kilka pełnych iteracji z dzieleniem zmiennoprzecinkowym za każdym razem.
    3. Narzut interpretera potęguje różnicę — w Pythonie krotność spowolnienia jest wyższa niż w C++ (≈19× wobec ≈11×), ponieważ każda iteracja pętli while w czystym Pythonie kosztuje dodatkowo czas na interpretację bajtkodu — koszt ten mnoży się przez liczbę iteracji potrzebnych do zbieżności.
    4. Zbieżność kwadratowa działa zgodnie z teorią — mimo że algorytm wykonuje kilka pełnych „przebiegów” (nie jedną operację), liczba iteracji potrzebna do pełnej precyzji double pozostaje mała (6–7) niezależnie od wielkości liczby w badanym zakresie, co potwierdza wyprowadzenie z sekcji Podstawy matematyczne.
    5. Praktyczny wniosek: w kodzie produkcyjnym należy zawsze używać funkcji bibliotecznej sqrt() — jest szybsza, dokładniejsza w skrajnych przypadkach (nieskończoności, liczby ujemne, podprzepełnienia) i przetestowana przez dekady użycia. Wartość metody babilońskiej jest dziś przede wszystkim edukacyjna — uczy, jak „pod maską” działają metody numeryczne, na których opierają się też znacznie bardziej zaawansowane algorytmy (np. metoda Newtona w uczeniu maszynowym czy optymalizacji).

    09 Źródła

    1. Wikipedia — Methods of computing square roots, en.wikipedia.org/wiki/Methods_of_computing_square_roots
    2. Wikipedia (PL) — Pierwiastek kwadratowy, pl.wikipedia.org/wiki/Pierwiastek_kwadratowy
    3. Wikipedia — YBC 7289 (babilońska tabliczka z przybliżeniem √2), en.wikipedia.org/wiki/YBC_7289
    4. Wikipedia — Newton's method, en.wikipedia.org/wiki/Newton's_method
    5. Wikipedia — Fast inverse square root, en.wikipedia.org/wiki/Fast_inverse_square_root
    6. Heron z Aleksandrii, Metrica (I w. n.e.) — historyczne źródło metody iteracyjnego uśredniania.
    7. D. E. Knuth, The Art of Computer Programming, vol. 2: Seminumerical Algorithms — rozdział o algorytmach arytmetycznych, w tym ekstrakcji pierwiastka.
    8. cppreference.com — std::sqrt, en.cppreference.com/w/cpp/numeric/math/sqrt
    9. Python documentation — math.sqrt, docs.python.org/3/library/math.html#math.sqrt
    10. Materiały własne projektu: kod źródłowy C++/Python, arkusz Excel i surowe wyniki pomiarów — katalogi code/ i experiment/ repozytorium projektu.