Почему программа не корректно вычисляет факториал больших чисел?

У вас есть интересная программа, которая предназначена для вычисления факториала любого числа, включая миллионы и более. Однако, существует проблема: программа корректно считает факториал для небольших чисел (однозначных или двухзначных), результаты совпадают с данными из интернета. Тем не менее, для больших чисел, таких как 1000, результаты оказались неправильными. В чем может быть причина этого явления и как можно это исправить? <br/><br/> Кроме того, мне было бы интересно узнать ваше мнение о данной программе и о возможных способах ее оптимизации для более быстрого вычисления. <br/><br/><pre><code class="cpp">#include &lt;vector&gt;
#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;mutex&gt;
#include &lt;fstream&gt;
#include &lt;sstream&gt;
#include &lt;iomanip&gt;
constexpr unsigned BASE = 1000000000;
std::mutex mtx;
void multiply_range(std::vector&lt;unsigned long long&gt;&amp; factorial, unsigned long long start, unsigned long long end) {
    unsigned long long carry = 0;
    for (unsigned long long i = start; i &lt;= end; ++i) {
        for (size_t j = 0; j &lt; factorial.size(); ++j) {
            unsigned long long result = factorial[j] * i + carry;
            factorial[j] = result % BASE;
            carry = result / BASE;
        }
        while (carry) {
            std::lock_guard&lt;std::mutex&gt; lock(mtx);
            factorial.push_back(carry % BASE);
            carry /= BASE;
        }
    }
}
std::vector&lt;unsigned long long&gt; fast_factorial(unsigned long long&amp; n) {
    std::vector&lt;unsigned long long&gt; factorial;
    factorial.reserve(1 + n);
    factorial.push_back(1);
    unsigned num_threads = std::thread::hardware_concurrency();
    std::vector&lt;std::thread&gt; threads;
    unsi
  • 16 октября 2024 г. 12:52
Ответы на вопрос 2
Ваша программа для вычисления факториалов больших чисел использует вектор для хранения результата, что уже является необходимым шагом, поскольку утечка памяти из-за использования стандартных типов данных (таких как `unsigned long long`) не позволила бы вам обрабатывать столь большие значения, как факториал числа 1000 или больше. Однако есть несколько моментов, которые могут влиять на правильность вычислений:

1. **Переполнение при умножении**: При умножении больших чисел, которые уже хранятся в векторе, могут происходить переполнения. Так как `result` также является `unsigned long long`, стоит учитывать, что эта переменная может не сохранять результат умножения, особенно при быстром увеличении значений.

2. **Проблема с переносом (carry)**: Переменная `carry` может быть неправильно обработана или не добавлена в конец вектора `factorial`, что может привести к неправильному результату. Это особенно актуально, если вектор `factorial` не был должным образом увеличен для учета переноса.

3. **Параллелизация**: Ваша программа пытается использовать многопоточность, что может быть сложнее в реализации, особенно когда дело доходит до добавления `carry`. Правильный порядок выполнения и синхронизация потоков могут быть нарушены, что также может вызывать ошибки.

4. **Неправильное резервирование памяти**: Ваша программа вызывает `factorial.reserve(1 + n)`, что вызывает выделение недостаточного объема памяти для хранения результативного факториала, особенно для больших чисел.

### Рекомендации по исправлению:

1. **Замена типа данных:** Используйте библиотеку, такую как GMP (GNU Multiple Precision Arithmetic Library), которая специально разработана для работы с большими числами, и устраните необходимость в ручном управлении переменными.

2. **Корректировка переноса:** Пересмотрите код работы с `carry`, чтобы убедиться, что он правильно добавляется к вектору. Возможно, стоит обрабатывать `carry` отдельно после основного цикла.

3. **Оптимизация параллелизма:** Сделайте так, чтобы каждый поток работал универсально и мог делиться результатами, чтобы избежать сложностей с синхронизацией.

4. **Избегание переполнений:** Проверьте все операции на возможность переполнения и дайте понять, что нужно будет сделать, когда это произойдет.

5. **Улучшение структуры алгоритма:** Если использовать параллелизм, реорганизуйте алгоритм так, чтобы он разделял работу по умножению, а затем объединял результаты.

Вот пример, как можно переписать часть вашей функции с учетом переносов:

```cpp
void multiply_range(std::vector<unsigned long long>& factorial, unsigned long long start, unsigned long long end) {
    for (unsigned long long i = start; i <= end; ++i) {
        unsigned long long carry = 0;
        for (size_t j = 0; j < factorial.size(); ++j) {
            unsigned long long result = factorial[j] * i + carry;
            factorial[j] = result % BASE;
            carry = result / BASE;
        }
        while (carry != 0) {
            factorial.push_back(carry % BASE);
            carry /= BASE;
        }
    }
}
```

### Заключение

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

Для записи факториала числа 1000 потребуется 2 568 цифр
https://zeptomath.com/calculators/factorial.php?nu...

А в программе используется тип: unsigned long long, а для него максимальное значение: 18 446 744 073 709 551 615
В данном типе невозможно сохранить результат вычисления, идет переполнение.

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