Strona główna▸Artykuły▸Fraktale

Zbiór Mandelbrota: Niekończące się szczegóły z z² + c

Algorytm czasu ucieczki, dokładne ograniczenie promienia 2, gładkie kolorowanie, sprawdzanie kształtu kardioidu i trik z zaburzeniem podczas głębokich powiększeń.

mysimulator teamZaktualizowano — czerwiec 2026≈ 11 min czytania▶ Otwórz symulację

Jedno arytmetyczne działanie powtarzane

Zbiór Mandelbrota jest zdefiniowany przez iterację tak krótką, że mieści się w tweecie. Weź liczbę zespoloną c. Zacznij od z = 0 i stosuj, raz po razie:

z_{n+1} = z_n² + c gdzie z_0 = 0 c należy do zbioru ⟺ sekwencja pozostaje ograniczona na zawsze c nie należy do zbioru ⟺ sekwencja ucieka w nieskończoność To jest cała definicja. Każda spirala, każdy filament, każda miniaturka kopii zbioru ukryta pod trilionem zoomów głęboko wewnątrz jest konsekwencją kwadratowania i dodawania. Zbiór nazwano na cześć Benoïta Mandelbrota, który wygenerował pierwsze obrazy tego zbioru w IBM w 1980 roku i dostrzegł, co tam widział; podległe dynamiki zespolone były badane przez Pierre Fatou i Gastona Jullię około 1918 roku, całkowicie bez komputerów.

z_{n+1} = z_n² + c        with z_0 = 0

c is IN the set     ⟺  the sequence stays bounded forever
c is OUT of the set ⟺  the sequence runs away to infinity
demo na żywo · powiązana symulacja● LIVE

Algorytm czasu ucieczki

Nie można iterować w nieskończoność, dlatego potrzebujesz testu, który kończy się. Zapewnia go mały twierdzenie: jeśli |z| kiedykolwiek przekroczy 2, sekwencja jest gwarantowany rozbieżności i nigdy nie powróci. Powód jest prosty – gdy |z| > 2 i |z| ≥ |c|, kwadratowanie rośnie szybciej niż dodawanie c może je ograniczyć. Zatem promień ucieczki wynosi 2, a nie jest to przybliżenie.

funkcja escape(cx, cy, maxIter) { let x = 0, y = 0, x2 = 0, y2 = 0, i = 0; while (x2 + y2 <= 4 && i < maxIter) { // porównanie kwadratów: bez pierwiastka y = 2 * x * y + cy; // 3 mnożenia na x = x2 - y2 + cx; // iterację, a nie 4 x2 = x * x; y2 = y * y; i++; } return i; // i === maxIter → załóż, że wewnątrz }

function escape(cx, cy, maxIter) {
  let x = 0, y = 0, x2 = 0, y2 = 0, i = 0;
  while (x2 + y2 <= 4 && i < maxIter) {   // compare squares: no sqrt
    y  = 2 * x * y + cy;                  // 3 multiplications per
    x  = x2 - y2 + cx;                    // iteration, not 4
    x2 = x * x;
    y2 = y * y;
    i++;
  }
  return i;                               // i === maxIter → assume inside
}

Jak to sprawić, żeby było gładko i szybko

Kolorowanie na podstawie surowego licznika całkowitego i jest generowane twarde, koncentryczne paski, ponieważ i jest funkcją krokową. Rozwiązaniem jest znormalizowana liczba iteracji, która interpoluje między paskami w oparciu o to, jak daleko poza promień ucieczki przeszedł ostatecznie z >z<. // po pętli uciekł z |z|² = x2 + y2 const logZn = Math.log(x2 + y2) / 2; const nu = Math.log(logZn / Math.log(2)) / Math.log(2); const smooth = i + 1 - nu; // liczba rzeczywista, a nie całkowita Podaj tę rzeczywistą liczbę jako podliczniki do palety i paski rozpuszczają się w ciągłą gradient. Dwa inne standardowe przyspieszenia mają znacznie większe znaczenie niż optymalizacja na poziomie mikroskopowym wewnętrznej pętli:

wewnętrzne kontrole Główny kardioida i okresowy bulbi drugiego stopnia mają dokładne algebraiczne testy. Punkty wewnątrz nich znajdują się w zbiorze, więc pomiń iterację całkowicie. To samo usuwa większość pracy ograniczonej maxIter w pełnym zestawie. podawanie cykli Ograniczony orbitą ostatecznie cykluje. Utrzymuj odniesienie detection) orbit powraca do zewnątrz epsilon od niego — punkt jest w zestawie i nie ma potrzeby osiągnąć maxIter. Test kardioidy wart jest zapisu, ponieważ jest tani i oszczędza dużo pracy.

// after the loop escaped with |z|² = x2 + y2
const logZn = Math.log(x2 + y2) / 2;
const nu    = Math.log(logZn / Math.log(2)) / Math.log(2);
const smooth = i + 1 - nu;                // a real number, not an integer

Co właściwie stanowi granica

Wnętrze jest matowe; cała struktura znajduje się na granicy. Trzy fakty, które warto zapamiętać. Po pierwsze, zbiór jest połączony – Adrien Douady i John Hubbard udowodnili to w 1982 roku. Wyspy, które widzi się pływające w czarnym morzu podczas dużego powiększenia, nie są wyspami: są to połączenia główną masą przez włókna zbyt cienkie, aby je wyświetlić.

Po drugie, granica ma wymiar Hausdorffa dokładnie równy 2 – Mitsuhiro Shishikura, 1998 – pomimo braku powierzchni. Jest tak zgięta jak jakaś krzywa w płaszczyźnie może być. Po trzecie, zbiór jest samo-podobny tylko przybliżnie: małe kopie rozsiane po włóknach nie są dokładnymi skalowaniami całego zbioru i każda z nich jest inaczej udekorowana. Zbiór Mandelbrota jest fraktalem w sensie nieskończonego szczegółu na każdym poziomie, a nie w sensie dokładnej samo-podobieństwa jak trójkąt Sierpińskiego.

Istnieje również bezpośrednia relacja z zbiorami Julia, które używają tej samej iteracji, ale utrzymują c stałe i zmieniają z₀. Zbiór Mandelbrota jest dokładnie mapą, która c daje połączony zbiór Julia: wybierz c wewnątrz niego i odpowiadający mu zbiór Julia będzie jednym ciągłym kawałkiem; wybierz go poza nim i zbiór Julia rozpada się na kurz Cantora. Każdy punkt zbioru Mandelbrota jest miniaturowym indeksem do innego zbioru Julia – dlatego eksplorator pokazujący oba strony obok siebie jest znacznie bardziej pouczający niż którykolwiek z nich osobno.

Ściana liczb zmiennoprzecinkowych

Przyciągnij się wystarczająco daleko i obraz zamienia się w bezkształtny bąk — nie dlatego, że zestaw wyczerpuje szczegółowość, ale ponieważ podwójna precyzja wyczerpuje cyfry. 64-bitowe podwojne liczby przenoszą około 15–16 znaczących cyfr dziesiętnych, więc gdy szerokość widoku spadnie do w przybliżeniu 10⁻¹⁵ oryginalnych sąsiednich pikseli, zaokrąglają się one do tej samej wartości i obraz staje się blokowy.

Poważni głębokie renderery zoomu unikają tego dzięki teorii perturbacji: oblicz jeden orbitę o wysokiej precyzji w centrum widoku przy użyciu arytmetyki zmiennoprzecinkowej, a następnie wyraź każdy inny piksel jako mały delta względem tej orbity i iteruj deltę przy użyciu zwykłej podwójnej precyzji. Delty pozostają małe, więc strata dokładności nie jest znacząca, a potrzebna jest tylko jedna orbita z wolnym obliczeniem dużych liczb. Dodaj szereg aproksymacji, aby pominąć pierwsze tysiące iteracji każdej orbity delta, a zoomy daleko poza 10⁻³⁰⁰ stają się praktyczne — co czyni głębokie filmy zoomowe, które widziałeś, możliwymi. Jedną wadą są artefakty: piksele, których orbita przechodzi zbyt blisko zera względem odniesienia, tracą dokładność i muszą być wykrywane i ponownie renderowane przeciwko drugiemu odniesieniu.

Frequently asked questions

Dlaczego promień ucieczki jest dokładnie równy 2?

Wynika to z faktu, że gdy moduł |z| przekracza 2 (i |z| ≥ |c|), kwadratowanie powoduje szybsze wzrost modułu niż dodawanie c może go przyciągnąć z powrotem, co gwarantuje, że sekwencja ucieknie w nieskończoność. Jest to udowodniony próg, a nie empiryczna wartość – żaden orbit, który opuści dysk o promieniu 2, nigdy nie powróci.

Dlaczego mój zoom głęboki zamienia się w bloki?

Dwupozycyjne zmiennoprzecinkowe liczby zmiennopunktowe przenoszą tylko około 15-16 cyfr znaczących. Gdy obszar widzenia wynosi około 10⁻¹⁵ oryginalnej szerokości, sąsiednie piksele zaokrąglają się do tego samego współrzędnego. Renderery deep-zoom unikają tego dzięki teorii perturbacji: jeden orbit o arbitralnej precyzji plus małe delta na piksel iterowane w podwójnej precyzji.

Czy zbiór Mandelbrota jest samopodobny?

Nie do końca. Posiada nieskończoną szczegółowość na każdym poziomie i zawiera niezliczone małe kopie samego siebie, ale te kopie są zniekształcone i każda jest inna – dlatego nie jest tożsamość samopodobieństwa w taki sposób jak trójkąt Sierpińskiego lub krzywa Koch. Jego granica ma wymiar Hausdorffa 2, podczas gdy otacza zero powierzchni.

Wypróbuj na żywo

Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz Mandelbrot Set Explorer i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.

▶ Otwórz symulację Mandelbrot Set Explorer

Co znalazłeś?

Dodaj kroki odtworzenia (opcjonalnie)