Самый быстрый факториал
Столкнулся с интересной задачкой. Как посчитать факториал числа самым быстрым способом? Быстрее чем за O(n). Подкиньте идею.
Столкнулся с интересной задачкой. Как посчитать факториал числа самым быстрым способом? Быстрее чем за O(n). Подкиньте идею.
Самый быстрый алгоритм вычисления факториала числа с типом int
- это использование таблицы. Так как переполнение int
приводит к неопределенному поведению (UB), то максимальное значение факториала ограничено INT_MAX.
Для 32-разрядного int
максимальный факториал это fac(12) = 479001600
, по этому самая быстрая функция вычисления факториала int32_t
выглядит так:
std::int32_t fac(std::int32_t x) {
static const int table[] = {
1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600,
};
assert(x >= 0);
assert(x <= 12);
return table[x];
}
Для unsigned int
всё интереснее, он может переполняться, но fac(34)
имеет множитель 2^32:
fac(34) = 0xde1bc4d19efcac82445da75b'0000'0000
Поэтому начиная с 34 все результаты fac(uint32_t)
будут равны нулю.
Для 64-разрядных чисел переполнение происходит после fac(20)
, нули начиная с fac(66)
.
Таким образом, использование таблицы факториалов из 66 элементов покроет всех типы до 64 разрядов:
#include <cassert>
#include <cstdint>
#include <iostream>
// Таблица длинная, по этому посчитаем ее во время компиляции (С++14).
template<int N>
struct Table {
constexpr Table() : t() {
t[0] = 1;
for (auto i = 1; i < N; ++i)
t[i] = t[i - 1] * i;
}
std::uint64_t t[N];
};
template<typename T>
T fac(T x) {
static_assert(sizeof(T) <= sizeof(std::uint64_t), "T is too large");
constexpr auto table = Table<66>();
assert(x >= 0);
return x < 66 ? static_cast<T>(table.t[x]) : 0;
}
int main() {
for (unsigned long long i = 0; i != 70; ++i) {
std::cout << i << ": " << fac(i) << '\n';
}
}
Вот есть интересная статейка. Там предлагают считать факториал за O(loglogn*M(nlogn))
(где M(n)
— время перемножения двух n-значных чисел). Быстрее вряд ли получится.
Также гляньте вот эту статью — там считают факториал по простому модулю.
Самый быстрый факториал - факториал, посчитанный во время компиляции программы. В С++ для этого можно использовать шаблоны. Взял и скопипастил пример сюда:
#include <iostream>
template<int N>
struct Factorial
{
enum { value = N * Factorial<N-1>::value };
};
template<>
struct Factorial<1>
{
enum { value = 1 };
};
int main()
{
// Example use
const int fact5 = Factorial<5>::value;
std::cout << fact5;
return 0;
}
PS: из недостатков конкретно этого вариант - очень быстро можешь вылезть за пределы допустимых значений для встроенных типов. enum же вроде как int работает.
Есть интересные материалы по этому поводу. Во-первых, есть калькулятор факториалов на JavaScript. Он мгновенно вычисляет факториалы чисел до 9.999.999.999
Там же есть материалы по алгоритмам факториалов: http://www.luschny.de/math/factorial/FastFactorialFunctions.htm. Я думаю, тут действительно хорошие алгоритмы ;)
На этом же сайте описывается некий метод, который, как я понял использовался в калькуляторах HP. Некий RPN-калькулятор.
Думаю, что самый быстрый алгоритм вычисления факториала определяется структурой вычислительных средств.
Например, формула Стирлинга допускает представление вида
n! ~ S(n) = a(n/e)n+½, где a= sqrt (2·pi·e).
log2 S(n) = ((n+½)( ln n - 1 ) + ln a)· log2e = L(n), S(n)= exp ({L(n)}·ln2)·2 [L(n)].
При этом двоичная запись факториала оканчивается на B(n)=[n/2]+[n/22]+[n/23]+... нулей (например, B(2k)=[2k/2]+[2k/22]+...+2+1 = 2k-1), и поэтому множитель при ней целый.
В итоге получаем формулу для быстрого факториала как целого числа с бинарным масштабным коэффициентом.
n! = [a·exp(L(n)·ln2)·2L(n)-B(n)+½]·2B(n),
которая при правильно выбранной точности вычислений будет точной.