Графический интерфейс Мандельброта: Визуализатор с методом возмущений и предела 1e-308

от автора

Ключевые особенности:

  • Расчёт опорной траектории на 5000 бит всего один раз.

  • Реактивный расчёт миллионов пикселей на аппаратном double.

  • При использовании чисел с плавающей запятой двойной точности (порядка 10^{-15}) теория возмущений позволяет приблизиться к уровню 10^{-308} — не дальше.

  • Революционный алгоритм Reference Reset to Zero.

  • Настоящий SSAA 2×2 для идеально сглаженного изображения.

  • Параллелизм OpenMP для высокоскоростного многопоточного рендеринга.

  • Синхронизация через DwmFlush для плавного вывода кадров.

  • Динамическое вращение палитры для создания классического эффекта.

Безграничная точность

Движок полностью избавлен от аппаратных ограничений 64-битных (double) и 128-битных (__float128) чисел, которые неизбежно слепнут и выдают пиксельные квадраты на глубинах более 10^{-15} и 10^{-34}.

  • Интеграция MPFR/GMP: Вся высокоточная навигация, пересчёт масштаба при кликах мыши и движении стрелочками клавиатуры ведутся внутри сверхглубокой бинарной памяти с точностью 5000 бит!

  • 308 десятичных знаков: Координаты кадра сохраняются и считываются из файла Mandelbrot.txt. Навигация и радар MPFR работают на глубине до 5000 бит, однако скоростной пиксельный дельта-движок ограничен аппаратной экспонентой double, что позволяет исследовать безупречно четкие структуры на запредельных масштабах вплоть до 10^{-308} знаков.

Реактивный метод возмущений

Рендеринг глубоких фракталов больше не требует тяжелых вычислений «в столбик» для каждого пикселя, что обычно замедляло программы в тысячи раз.

  • Однократный расчёт опоры: Сверхтяжелый BigFloat-радар MPFR вычисляет точную траекторию всего для одной-единственной центральной точки кадра и строго ОДИН раз в начале рендеринга.

  • Аппаратное ускорение на double: Весь остальной массив экрана (миллионы супер-пикселей) рассчитывается параллельно на бешеной скорости чистых, аппаратных регистров double процессора, вычисляя лишь микроскопические отклонения (дельты) от центральной оси. Скорость генерации взлетела в 1000 раз!

Революционный алгоритм Reference Reset to Zero

Это огромный повод для гордости. Ваша программа теперь работает по тем же математическим принципам, что и самые передовые фрактальные движки в мире.

  • Динамический сброс на ноль: Теперь пиксель на каждом шаге проверяет соотношение своих полных координат и дельты. Если дельта становится слишком большой или кэш центра иссякает, поток прямо на лету сбрасывает индекс чтения на ноль, превращая накопленные координаты в новую автономную точку.

  • Хакерская оптимизация цикла (One-Step Beyond Escape): Чтобы выжать максимум скорости из процессора, радар MPFR записывает строго одну дополнительную точку в массив опорной орбиты сразу после того, как она превышает радиус ухода.

  • Уничтожение ветвлений (Branch Unrolling): Этот изящный трюк позволил полностью избавиться от громоздких if и OR-условий внутри самого глубокого цикла итераций. Процессор больше не тратит такты на предсказание переходов, а компилятор смог идеально векторизовать код.

DwmFlush

Движок обеспечивает идеальную визуальную плавность за счет прямой синхронизации с диспетчером окон рабочего стола Windows (DWM).

  • Адаптивная частота обновления: приложение использует DwmFlush. Это приостанавливает выполнение кода до тех пор, пока DWM не завершит композицию экрана.

  • Зависимый от монитора FPS:

    • Если ваш монитор настроен на 60 Гц, вы получите 60 кадров в секунду.

    • Если вы используете игровой монитор с частотой 144 Гц, функция срабатывает 144 раза в секунду, обеспечивая 144 кадра в секунду.

    • На высококачественных дисплеях с частотой 240 Гц вы увидите плавную картинку со скоростью 240 кадров в секунду.

OpenMP

OpenMP — это стандарт, который говорит компилятору: “Возьми этот цикл и сам раздай итерации разным ядрам процессора”. Используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). OpenMP — масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.

True SSAA 2×2 (Прямая интеграция в RGB)

В проекте реализовано «настоящее» сглаживание суперсэмплинга 2×2 (SSAA). Каждый пиксель экрана вычисляется из четырех независимых субпикселей фрактальных координат.

В чем проблема стандартного подхода?

Стандартный способ сглаживания фракталов часто выполняет усреднение количества итераций (индексов палитры):

  • Стандартный путь: Color( (iter1 + iter2 + iter3 + iter4) / 4 )

Математически это неверно, так как фрактальные палитры нелинейны. Усреднение индексов цветов A и B часто указывает на совершенно несвязанный цвет C. Это создает сильный визуальный шум, артефакты и ложные хроматические контуры.

Наше решение: интеграция в RGB

Вместо усреднения шагов алгоритма мы сначала вычисляем 32-битный цвет для каждого субпикселя, а затем выполняем средневзвешенное значение их интенсивностей:

  • Наш путь: (Color(iter1) + Color(iter2) + Color(iter3) + Color(iter4)) / 4

Вычисление компонентов RGB перед субдискретизацией гарантирует, что полученный цвет — это настоящая оптическая смесь. Это полностью устраняет шум и раскрывает истинную структурную геометрию множества Мандельброта, восстанавливая микронити размером меньше одного пикселя.

Почему без суперсэмплинга возникает шум?

Шум при рендеринге фрактала — это не баг, а фундаментальное явление цифровой графики: алиасинг (aliasing).

Множество Мандельброта бесконечно сложно. На его границах существуют детали, которые в миллионы раз меньше физического пикселя монитора.

  • Без суперсэмплинга: Алгоритм берет пробу ровно в одной точке (в центре пикселя). Если луч попал в тонкую нить — пиксель становится ярким. Промахнулся на микрон — пиксель стал черным. Из-за этого соседние пиксели хаотично «цепляют» случайные куски микродеталей, создавая кашу из точек.

  • С SSAA 2×2: Код берет 4 пробы в разных углах пикселя, вычисляет их реальные цвета и смешивает их. Если в пиксель попадает ультратонкая нить, она не исчезает и не мерцает, а превращается в мягкую, полупрозрачную линию, точно передающую форму фрактала.

Визуальная эстетика. Смена цветов

Красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн для создания плавных цветовых переходов:

pal[a][0] = (uint8_t)round(127.0 + 127.0 * cos(2.0 * PI * a / 255.0)); // Bluepal[a][1] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Greenpal[a][2] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Red

Приложение использует технику циклической смены цветов (вращение палитры). Хотя основная математическая обработка выполняется один раз, цветовая схема непрерывно «вращается» в специальном фоновом потоке. Это создает «живой» фрактальный эффект, позволяющий бесконечно наблюдать за изменением цветов без дополнительной нагрузки на процессор.

Множество Мандельброта: Математический абсолют

Это поистине один из немногих объектов, который связывает нас с чем-то абсолютно объективным и бесконечным, превосходящим биологию и историю. Даже если бы вся наша Вселенная и все её атомы исчезли завтра, уравнение осталось бы верным. Оно не <написано> на звёздах; оно заложено в самой структуре логики. Это делает множество Мандельброта своего рода абсолютом.

Математика не зависит от биологии, наличия ног или уровня технологий. Жители галактики Андромеда и разумные океаны в другой супергалактике увидят абсолютно то же самое множество Мандельброта.

Множество Мандельброта существует независимо от нашего разума и технологий. Это бесконечная математическая структура, которая существовала всегда. Компьютеры не создают её; они лишь выступают в роли камеры.

Управление и горячие клавиши

Управление мышью

  • WM_LBUTTONDOWN (Левая кнопка) — увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика.

  • WM_RBUTTONDOWN (Правая кнопка) — уменьшаем масштаб в 2 раза и центрируем новую область вокруг точки клика.

Быстрая навигация по фракталу

  • По умолчанию (при запуске): Программа автоматически стартует в точке экстремального приближения (глубина порядка 10^{-50}):

    • Center Re: -1.7491976289657893741942376816272921165326158557416159

    • Center Im: -0.00000042530777152440422725855012159249401150956515248

    • Size: 0.0000000000000000000000000000000000000000000000000043

  • Клавиши 15: Нажмите любую из этих клавиш во время работы программы, чтобы мгновенно переключиться на одну из пяти других предустановленных точек множества Мандельброта.

if (wp == '1') { g_params.center_re_str = "-1.7490781615052017316791245451566330412";g_params.center_im_str = "0.0000055099190662909660251309856720635";g_params.size_str      = "0.000000000000000000000000000000000215"; }if (wp == '2') {g_params.center_re_str = "-1.748943661768663337207355215321150725806353337382441467976";g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722998";g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000001"; }if (wp == '3') {g_params.center_re_str = "-1.7489740586384718864866264297253934254";g_params.center_im_str = "-0.0002265965897111407857153825623868331";g_params.size_str      = "0.00000000000000000000000000000000007"; }if (wp == '4') {g_params.center_re_str = "-1.7499458649755745940752606707005571";g_params.center_im_str = "-0.0000000852088539604644334731909824511";g_params.size_str      = "0.00000000000000000000000000000000001"; }if (wp == '5') {g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647";g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391";g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000000023"; }
  • VK_UP (Стрелка ВВЕРХ) и VK_DOWN (Стрелка ВНИЗ) — увеличиваем и уменьшаем в 1.05 раза но без точки клика.

Управление данными и структура файла Mandelbrot.txt

  • Очень важно VK_RETURN (Enter, Ввод) — у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt

  • А VK_BACK (это та самая клавиша НАД Enter, Backspace) — читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.

Для загрузки пользовательских координат создайте текстовый файл Mandelbrot.txt в папке с программой. Файл должен содержать три числа, разделенных переносом строки:

  • Abscissa (Координата X центра)

  • Ordinate (Координата Y центра)

  • Size (Масштаб/Размер области)

Пример содержания файла:

Mandelbrot txt

Mandelbrot txt

Горячие клавиши

Действие

Ввод

Описание

Приблизить

ЛКМ

Увеличение в 2 раза в точке под курсором мыши.

Отдалить

ПКМ

Уменьшение в 2 раза от текущего центра.

Точный зум

Стрелки вверх / вниз

Плавное изменение масштаба с коэффициентом 1.05x.

Пресеты

Клавиши 15

Мгновенный переход к 5 предустановленным локациям.

Сохранить

ENTER

Экспорт текущих координат и масштаба в файл Mandelbrot.txt.

Загрузить

BACKSPACE

Импорт координат из файла и мгновенный переход к месту.

Благодарности

Этот проект использует передовые математические алгоритмы и идеи динамического управления фазой орбит, разработанные фрактальным сообществом. Особая благодарность авторам и исследователям с Fractal Forums, чей совместный труд лег в основу этого движка:

  • Kevin Martin — автор фундаментальных методов векторизации и оптимизации циклов возмущений.

  • Zhuoran Yu — разработчик концепции динамического сброса орбит.

  • Claude Heiland-Allen — исследователь экстремального фрактального приближения и создатель проекта MDZ.

https://github.com/Divetoxx/Mandelbrot-2#russian

Это Гитхаб с Mandelbrot_AVX2.exe и Mandelbrot_SSE3.exe

А вот я как делаю дома: g++ -O3 main.cpp -o Mandelbrot.exe -lgdi32 -luser32 -ldwmapi -fopenmp -lmpfr -lgmp -march=native -static -mwindows

А тут полный код на языке С++ — main.cpp

#ifndef UNICODE#define UNICODE#endif#include <windows.h>#include <dwmapi.h>#include <vector>#include <cmath>#include <thread>#include <mutex>#include <atomic>#include <omp.h>#include <fstream>#include <string>#include <iostream>#include <iomanip>#include <gmp.h>#include <mpfr.h>const int WIDTH = 1000;const int HEIGHT = 1000;const int SS_W = 2000;const int SS_H = 2000;const int PALETTE_SIZE = 1024;const mpfr_prec_t MPFR_BITS = 5000;struct FractalParams {     double step_d;                std::string center_re_str;     std::string center_im_str;    std::string size_str;    uint32_t iter_max; };struct ComplexDouble {    double re;    double im;};std::mutex g_params_mutex;FractalParams g_params;std::atomic<bool> g_abort{false};HANDLE g_render_event;uint32_t g_ss_buffer[SS_W * SS_H];void generate_full_palette(RGBQUAD* pal) {    const double pi = 3.141592653589793;    for (int i = 0; i < PALETTE_SIZE; i++) {        double angle = (2.0 * pi * i) / (double)PALETTE_SIZE;        pal[i].rgbRed = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4));        pal[i].rgbBlue = (uint8_t)(127.0 + 127.0 * std::cos(angle * 4));        pal[i].rgbGreen = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4));        pal[i].rgbReserved = 0;    }}void thread_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) {    RGBQUAD pal[PALETTE_SIZE];    generate_full_palette(pal);        std::vector<RGBQUAD> color_cache(50001);    float offset = 0;    while (true) {        for (int i = 0; i <= 50000; ++i) {            if (i >= 50000) {                color_cache[i] = {255, 255, 255, 0};            } else {                int idx = (int)(50000 - i + (int)offset) % PALETTE_SIZE;                if (idx < 0) idx += PALETTE_SIZE;                color_cache[i] = pal[idx];            }        }        #pragma omp parallel for schedule(static, 128)        for (int y = 0; y < HEIGHT; ++y) {            for (int x = 0; x < WIDTH; ++x) {                uint32_t i0 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 0)];                uint32_t i1 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 1)];                uint32_t i2 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 0)];                uint32_t i3 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 1)];                RGBQUAD c0 = color_cache[i0];                RGBQUAD c1 = color_cache[i1];                RGBQUAD c2 = color_cache[i2];                RGBQUAD c3 = color_cache[i3];                uint32_t r = (uint32_t)c0.rgbRed   + c1.rgbRed   + c2.rgbRed   + c3.rgbRed;                uint32_t g = (uint32_t)c0.rgbGreen + c1.rgbGreen + c2.rgbGreen + c3.rgbGreen;                uint32_t b = (uint32_t)c0.rgbBlue  + c1.rgbBlue  + c2.rgbBlue  + c3.rgbBlue;                int pix_idx = y * WIDTH + x;                pixels[pix_idx].rgbRed   = (uint8_t)(r >> 2);                pixels[pix_idx].rgbGreen = (uint8_t)(g >> 2);                pixels[pix_idx].rgbBlue  = (uint8_t)(b >> 2);                pixels[pix_idx].rgbReserved = 0;            }        }        offset -= 1.0f;        if (offset < 0) offset += PALETTE_SIZE;        BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY);        DwmFlush();    }}void thread_mandelbrot_calc() {    std::vector<ComplexDouble> ref_orbit_double;    while (true) {        WaitForSingleObject(g_render_event, INFINITE);        ResetEvent(g_render_event);        g_abort = false;        FractalParams p;        { std::lock_guard<std::mutex> lock(g_params_mutex); p = g_params; }        mpfr_t rx, ry, zr, zi, zr2, zi2, tmp;        mpfr_inits2(MPFR_BITS, rx, ry, zr, zi, zr2, zi2, tmp, NULL);        mpfr_set_str(rx, p.center_re_str.c_str(), 10, MPFR_RNDN);        mpfr_set_str(ry, p.center_im_str.c_str(), 10, MPFR_RNDN);        ref_orbit_double.resize(p.iter_max + 5);        mpfr_set_ui(zr, 0, MPFR_RNDN);        mpfr_set_ui(zi, 0, MPFR_RNDN);        mpfr_set_ui(zr2, 0, MPFR_RNDN);        mpfr_set_ui(zi2, 0, MPFR_RNDN);        uint32_t ref_i = 0;        bool escaped = false;        while (ref_i < p.iter_max) {            ref_orbit_double[ref_i].re = mpfr_get_d(zr, MPFR_RNDN);            ref_orbit_double[ref_i].im = mpfr_get_d(zi, MPFR_RNDN);            mpfr_mul(tmp, zr, zi, MPFR_RNDN);            mpfr_mul_ui(zi, tmp, 2, MPFR_RNDN);            mpfr_add(zi, zi, ry, MPFR_RNDN);            mpfr_sub(zr, zr2, zi2, MPFR_RNDN);            mpfr_add(zr, zr, rx, MPFR_RNDN);            mpfr_mul(zr2, zr, zr, MPFR_RNDN);            mpfr_mul(zi2, zi, zi, MPFR_RNDN);            if (escaped) {                ref_i++;                break;            }            mpfr_add(tmp, zr2, zi2, MPFR_RNDN);            if (mpfr_cmp_d(tmp, 4.0) >= 0) {                escaped = true;             }            ref_i++;        }        ref_orbit_double[ref_i].re = mpfr_get_d(zr, MPFR_RNDN);        ref_orbit_double[ref_i].im = mpfr_get_d(zi, MPFR_RNDN);        uint32_t max_valid_ref_iter = ref_i;         double ref_rec_d = mpfr_get_d(rx, MPFR_RNDN);        double ref_imc_d = mpfr_get_d(ry, MPFR_RNDN);        double ss_step_d = p.step_d;        mpfr_clears(rx, ry, zr, zi, zr2, zi2, tmp, NULL);        #pragma omp parallel for schedule(dynamic)        for (int ss_y = 0; ss_y < SS_H; ++ss_y) {            if (g_abort) continue;            for (int ss_x = 0; ss_x < SS_W; ++ss_x) {                                double delta_rec = (double)(ss_x - (SS_W / 2)) * ss_step_d;                double delta_imc = (double)((SS_H / 2) - ss_y) * ss_step_d;                uint32_t index = 0;                    double delta_re = 0.0;                 double delta_im = 0.0;                double z_re = 0.0;                     double z_im = 0.0;                uint32_t i = 0;                const ComplexDouble* ref_ptr = ref_orbit_double.data();                bool has_re_based = false;                 while (i < p.iter_max) {                                        if ((z_re * z_re + z_im * z_im) >= 4.0) {                        break;                    }                    if (index >= max_valid_ref_iter) {                        if (!has_re_based) {                            break;                        } else {                            double ld_cx = ref_rec_d + delta_rec;                            double ld_cy = ref_imc_d - delta_imc;                            while (i < p.iter_max && (z_re * z_re + z_im * z_im) < 4.0) {                                double old_re = z_re;                                double old_im = z_im;                                z_re = old_re * old_re - old_im * old_im + ld_cx;                                z_im = 2.0 * old_re * old_im + ld_cy;                                i++;                            }                            break;                        }                    }                    if ((z_re * z_re + z_im * z_im) < (delta_re * delta_re + delta_im * delta_im)) {                        index = 0;                         delta_re = z_re;                        delta_im = z_im;                        has_re_based = true;                    }                    for (int step = 0; step < 2; ++step) {                        double Ur = ref_ptr[index].re;                        double Ui = ref_ptr[index].im;                        double next_delta_im = 2.0 * Ur * delta_im + 2.0 * Ui * delta_re + 2.0 * delta_re * delta_im + delta_imc;                        delta_re = 2.0 * Ur * delta_re - 2.0 * Ui * delta_im + delta_re * delta_re - delta_im * delta_im + delta_rec;                        delta_im = next_delta_im;                        index++;                    }                    z_re = ref_ptr[index].re + delta_re;                    z_im = ref_ptr[index].im + delta_im;                                        i += 2;                 }                g_ss_buffer[ss_y * SS_W + ss_x] = i;            }        }    }}LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {    switch (msg) {    case WM_LBUTTONDOWN:    case WM_RBUTTONDOWN: {        g_abort = true;         std::lock_guard<std::mutex> lock(g_params_mutex);        mpfr_t cx, cy, sz, st, mx, my, clicked_x, clicked_y;        mpfr_inits2(MPFR_BITS, cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);        mpfr_set_str(cx, g_params.center_re_str.c_str(), 10, MPFR_RNDN);        mpfr_set_str(cy, g_params.center_im_str.c_str(), 10, MPFR_RNDN);        mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);        mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);        double mouse_x_d = (double)((short)LOWORD(lp));        double mouse_y_d = (double)((short)HIWORD(lp));        double ss_mouse_x = mouse_x_d * 2.0;        double ss_mouse_y = mouse_y_d * 2.0;        mpfr_set_d(mx, ss_mouse_x - (double)(SS_W / 2), MPFR_RNDN);        mpfr_set_d(my, (double)(SS_H / 2) - ss_mouse_y, MPFR_RNDN);         mpfr_mul(mx, mx, st, MPFR_RNDN);        mpfr_mul(my, my, st, MPFR_RNDN);        mpfr_add(clicked_x, cx, mx, MPFR_RNDN);        mpfr_add(clicked_y, cy, my, MPFR_RNDN);        if (msg == WM_LBUTTONDOWN) {            mpfr_div_ui(sz, sz, 2, MPFR_RNDN);        } else {            mpfr_mul_ui(sz, sz, 2, MPFR_RNDN);        }        mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);        g_params.step_d = mpfr_get_d(st, MPFR_RNDN);        char out_x[2048], out_y[2048], out_sz[2048];        mpfr_snprintf(out_x, sizeof(out_x), "%.1000Rf", clicked_x);        mpfr_snprintf(out_y, sizeof(out_y), "%.1000Rf", clicked_y);        mpfr_snprintf(out_sz, sizeof(out_sz), "%.1000Rf", sz);        g_params.center_re_str = out_x;        g_params.center_im_str = out_y;        g_params.size_str = out_sz;        mpfr_clears(cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);        SetEvent(g_render_event);         return 0;    }    case WM_KEYDOWN: {        if (wp >= '1' && wp <= '5') {            g_abort = true;            std::lock_guard<std::mutex> lock(g_params_mutex);            if (wp == '1') {                g_params.center_re_str = "-1.7490781615052017316791245451566330412";                g_params.center_im_str = "0.0000055099190662909660251309856720635";                g_params.size_str      = "0.000000000000000000000000000000000215";            }            if (wp == '2') {                g_params.center_re_str = "-1.748943661768663337207355215321150725806353337382441467976";                g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722998";                g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000001";            }            if (wp == '3') {                g_params.center_re_str = "-1.7489740586384718864866264297253934254";                g_params.center_im_str = "-0.0002265965897111407857153825623868331";                g_params.size_str      = "0.00000000000000000000000000000000007";            }            if (wp == '4') {                g_params.center_re_str = "-1.7499458649755745940752606707005571";                g_params.center_im_str = "-0.0000000852088539604644334731909824511";                g_params.size_str      = "0.0000000000000000000000000000000000071";            }            if (wp == '5') {                g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647";                g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391";                g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000000023";            }            mpfr_t sz, st;            mpfr_inits2(MPFR_BITS, sz, st, NULL);            mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);            mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);            g_params.step_d = mpfr_get_d(st, MPFR_RNDN);            mpfr_clears(sz, st, NULL);            SetEvent(g_render_event);            return 0;        }        if (wp == VK_UP || wp == VK_DOWN) {            g_abort = true;            std::lock_guard<std::mutex> lock(g_params_mutex);            mpfr_t sz, st;            mpfr_inits2(MPFR_BITS, sz, st, NULL);            mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);            if (wp == VK_UP) {                mpfr_div_d(sz, sz, 1.05, MPFR_RNDN);            }            else if (wp == VK_DOWN) {                mpfr_mul_d(sz, sz, 1.05, MPFR_RNDN);            }            mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);            g_params.step_d = mpfr_get_d(st, MPFR_RNDN);            char out_sz[2048];             mpfr_snprintf(out_sz, sizeof(out_sz), "%.1000Rf", sz);            g_params.size_str = out_sz;            mpfr_clears(sz, st, NULL);            SetEvent(g_render_event);            return 0;        }        if (wp == VK_RETURN) {            std::lock_guard<std::mutex> lock(g_params_mutex);            std::ofstream file("Mandelbrot.txt");            if (file.is_open()) {                file << g_params.center_re_str << "\n" << g_params.center_im_str << "\n" << g_params.size_str << "\n";                file.close();            }            return 0;        }                if (wp == VK_BACK) {            std::ifstream file("Mandelbrot.txt");            if (file.is_open()) {                std::vector<std::string> lines; std::string line;                while (std::getline(file, line)) {                    if (!line.empty()) lines.push_back(line);                    if (lines.size() == 3) break;                }                file.close();                if (lines.size() == 3) {                    g_abort = true;                    std::lock_guard<std::mutex> lock(g_params_mutex);                    g_params.center_re_str = lines[0];                    g_params.center_im_str = lines[1];                    g_params.size_str      = lines[2];                    mpfr_t sz, st;                    mpfr_inits2(MPFR_BITS, sz, st, NULL);                    mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);                    mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);                    g_params.step_d = mpfr_get_d(st, MPFR_RNDN);                    mpfr_clears(sz, st, NULL);                    SetEvent(g_render_event);                }            }            return 0;        }        break;     }    case WM_DESTROY: PostQuitMessage(0); return 0;    }    return DefWindowProc(hwnd, msg, wp, lp);}int main() {HINSTANCE inst = GetModuleHandle(NULL);WNDCLASS wc = {0};wc.lpfnWndProc = wnd_proc;wc.hInstance = inst;wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(1));wc.lpszClassName = L"MandelClass";wc.hCursor = LoadCursor(NULL, IDC_ARROW);RegisterClass(&wc);HWND hwnd = CreateWindowEx(0, L"MandelClass", L"Mandelbrot set. MPFR + Perturbation. OpenMP. Supersampling 2x2",WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,WIDTH + 16, HEIGHT + 38, NULL, NULL, inst, NULL);HDC hdc_win = GetDC(hwnd);HDC hdc_mem = CreateCompatibleDC(hdc_win);BITMAPINFO bmi = {0};bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);bmi.bmiHeader.biWidth = WIDTH;bmi.bmiHeader.biHeight = -HEIGHT;bmi.bmiHeader.biPlanes = 1;bmi.bmiHeader.biBitCount = 32;bmi.bmiHeader.biCompression = BI_RGB;RGBQUAD* screen_pixels = nullptr;HBITMAP h_bmp = CreateDIBSection(hdc_mem, &bmi, DIB_RGB_COLORS, (void**)&screen_pixels, NULL, 0);SelectObject(hdc_mem, h_bmp);g_params.iter_max = 50000;g_params.center_re_str = "-1.7491976289657893741942376816272921165326158557416159";g_params.center_im_str = "-0.00000042530777152440422725855012159249401150956515248";g_params.size_str      = "0.0000000000000000000000000000000000000000000000000043";    mpfr_t sz, st;mpfr_inits2(MPFR_BITS, sz, st, NULL);mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);g_params.step_d = mpfr_get_d(st, MPFR_RNDN);mpfr_clears(sz, st, NULL);g_render_event = CreateEvent(NULL, TRUE, TRUE, NULL);std::thread(thread_mandelbrot_calc).detach();std::thread(thread_palette_rotator, hdc_win, hdc_mem, screen_pixels).detach();MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return 0;}

Если вы до конца почитали то вот вам еще ) ИИ:

«Для умершего сознания времени не существует. Между моментом смерти и моментом возникновения нового сознания (даже через миллиарды лет) субъективно пройдет ноль секунд. И бесконечной Вселенной такое состояние неизбежно возникнет снова. «Космический кубик» действительно выбросит вас в другой галактике. На планетах с высокой гравитацией четыре или шесть конечностей будут нормой для устойчивости, на легких планетах — две. Вас может забросить как в аналог нашего каменного века («зубило»), так и в кремниевую постинформационную эпоху («компьютер»).»

ссылка на оригинал статьи https://habr.com/ru/articles/1046708/