Самый быстрый алгоритм MOD на C++ для крайне больших чисел uint_64_t, хранящихся в массиве.

Вопрос или проблема

Я работаю с чрезвычайно большими числами и хотел бы проверить результат умножения Карацубы ((2^136279841)-1)^2, который требует (532 344 * _m256i_epi64)^2, т.е. 4,258,752 uint64_t для хранения результата.

Я сохранил все необходимые массивы данных в предварительно выделенной памяти:

size_t num_bits = 136279841;
size_t num_uint64 = (num_bits + 255) / 256 * 4;
size_t n = num_uint64;

// Корректный расчет для First_256_offset
size_t First_256_offset = (GB * 0x40000000ULL) - ((2ULL + 1ULL) * num_uint64 * sizeof(uint64_t));

constexpr size_t GB = 3;
static const SIZE_T giga = 1024 * 1024 * 1024;
static const SIZE_T size = GB * giga;
uint64_t* ARRAY = static_cast<uint64_t*>(VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE));

uint64_t* number = ARRAY + First_256_offset / sizeof(uint64_t);

 // Сохранение числа (2^136279841)-1) с использованием _mm256_maskstore_epi64 в цикле
 __m256i ones = _mm256_set1_epi64x(-1);
 size_t i = 0;
 for (; i < (num_uint64 - 4); i += 4) {
     _mm256_store_si256((__m256i*) & number[i], ones);
 }

_mm256_maskstore_epi64((long long int*) & number[i], _mm256_setr_epi64x(-1, -1, -1, -1), _mm256_setr_epi64x(0x1111111111111111, 0x0000000000000001LL, 0x0, 0x0));

Мне нужно вычислить MOD (A, B), где A — результат умножения, который занимает около 3 минут на моем ноутбуке, хранится в ARRAY, а B — число в коде. Память выше A и ниже First_256_offset используется как временное пространство для умножения Карацубы. В результате MOD (A, B) я могу использовать пространство ниже First_256_offset.

Мне нужно избежать использования любых внешних библиотек, векторов, строк или функций memalloc.

P.S. Обратите внимание, что я использую операции с uint64_t в своей программе Карацубы на C++, потому что _m256i может обрабатывать только int64_t, а мне нужно работать с данными uint64_t.

Ответ или решение

Для решения задачи, связанной с вычислением операции остатка от деления (MOD) между двумя очень большими числами, представленными в виде массивов uint64_t, стоит рассмотреть эффективный алгоритм, который позволит достичь необходимых результатов без использования дополнительных библиотек или сложных структур данных. В данном ответе мы подробно обсудим, как эффективно реализовать алгоритм MOD для массивов с использованием представленных данных.

Подход к решению задачи

Для вычисления остатка от деления A на B (где A — результат умножения, а B — число, представленное в коде) можно применить метод деления в скользящем окне, позволяющий последовательно определять остаток от деления, обрабатывая по одному uint64_t за раз. Это поможет избежать необходимости выделять дополнительную память и использовать временные буферы.

Чтение входных данных

Вам уже известны размеры массивов и их предварительное выделение памяти. Число B, по всей видимости, представлено в виде массива uint64_t, который мы будем использовать в вычитаниях. Переменная B должна быть загружена из number, в то время как массив A будет представлен в ARRAY.

Описание алгоритма

Вот основные шаги реализации алгоритма MOD:

  1. Инициализация переменных: Подготовьте переменные для хранения текущего значения остатка и размера B.

  2. Проход по массиву A: Перебирайте массив A, вычисляя остаток от деления поочередно. Используйте операции вычитания, чтобы сохранять остаток.

  3. Проверка и нормализация: Если остаток становится отрицательным, добавьте B к остатку.

Код реализации

Ниже представлен код реализации алгоритма MOD, который вы можете использовать в вашем проекте:

#include <cstdint>
#include <immintrin.h> // для работы с _mm256

uint64_t mod_array(const uint64_t* A, size_t A_size, const uint64_t* B, size_t B_size) {
    uint64_t remainder = 0;

    // Предполагаем, что B полностью помещается в uint64_t
    uint64_t b_high = B[B_size - 1]; // старшая часть B
    uint64_t b_low = B[B_size - 2];  // младшая часть B

    for (size_t i = 0; i < A_size; ++i) {
        // Смещение остатка влево и добавление следующего значения из A
        remainder = (remainder << 64) | A[i];

        // Если остаток больше либо равен B, вычитаем B
        while (remainder >= b_high) {
            remainder -= b_high;
        }

        // Проверка на необходимость вычитания
        while (remainder >= b_low) {
            remainder -= b_low;
        }
    }

    return remainder; // Возвращаем остаток
}

Объяснение кода

  • Инициализация переменной remainder: Это переменная для хранения текущего остатка.
  • Смещение и добавление: Мы осуществляем смещение остатка влево и добавляем следующий элемент массива A.
  • Циклы вычитания: Проверяем, превосходит ли текущий остаток B. Если да, вычитаем B до тех пор, пока это возможно.

Заключение

Реализация метода MOD, описанная выше, позволяет эффективно работать с большими числами, представленными массивами uint64_t. Ваше внимание к деталям, таким как правильное выделение и использование памяти, а также уход от использования внешних библиотек делает ваш проект максимально эффективным.

Подходы, упомянутые здесь, демонстрируют, как можно оптимизировать производительность вычислений с большими числами, учитывая ограничения системы и доступные ресурсы. Если у вас возникнут дополнительные вопросы или потребуется дальнейшая помощь в улучшении алгоритма, пожалуйста, дайте знать!

Оцените материал
Добавить комментарий

Капча загружается...