Вопрос или проблема
Я работаю с чрезвычайно большими числами и хотел бы проверить результат умножения Карацубы ((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:
-
Инициализация переменных: Подготовьте переменные для хранения текущего значения остатка и размера
B
. -
Проход по массиву A: Перебирайте массив A, вычисляя остаток от деления поочередно. Используйте операции вычитания, чтобы сохранять остаток.
-
Проверка и нормализация: Если остаток становится отрицательным, добавьте 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
. Ваше внимание к деталям, таким как правильное выделение и использование памяти, а также уход от использования внешних библиотек делает ваш проект максимально эффективным.
Подходы, упомянутые здесь, демонстрируют, как можно оптимизировать производительность вычислений с большими числами, учитывая ограничения системы и доступные ресурсы. Если у вас возникнут дополнительные вопросы или потребуется дальнейшая помощь в улучшении алгоритма, пожалуйста, дайте знать!