Рассчитать факториал произвольно большого числа, показывая все цифры

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

Я искал разные места и спрашивал на нескольких форумах. Но я хотел бы знать, есть ли способ сделать это без использования таких библиотек, как GMP.

Спасибо.

11 ответов

Решение

Библиотека GNU Multiprecision хороша! Но поскольку вы говорите, что использование внешних библиотек недопустимо, я полагаю, что это возможно только путем взятия массива типа int, а затем умножения чисел, как вы делаете это пером на бумаге!

Вот код, который я написал некоторое время назад..

#include<iostream>
#include<cstring>

int max = 5000;

void display(int arr[]){
    int ctr = 0;
    for (int i=0; i<max; i++){
        if (!ctr && arr[i])         ctr = 1;
        if(ctr)
            std::cout<<arr[i];
    }
}


void factorial(int arr[], int n){
    if (!n) return;
    int carry = 0;
    for (int i=max-1; i>=0; --i){
        arr[i] = (arr[i] * n) + carry;
        carry = arr[i]/10;
        arr[i] %= 10;
    }
    factorial(arr,n-1);
}

int main(){
    int *arr = new int[max];
    std::memset(arr,0,max*sizeof(int));
    arr[max-1] = 1;
    int num;
    std::cout<<"Enter the number: ";
    std::cin>>num;
    std::cout<<"factorial of "<<num<<"is :\n";
    factorial(arr,num);
    display(arr);
    delete[] arr;
    return 0;
}

'arr' - это просто целочисленный массив, а factorial - простая функция, которая умножает данное число на "большое число".

Надеюсь, что это решает ваш запрос..

Принятый ответ хорошо, но это C++; мы можем сделать лучше Давайте начнем наш собственный Bignum класс, с абсолютно неограниченным количеством цифр.

Для достижения максимальной эффективности мы будем работать с чистыми двоичными числами, упаковывая каждый элемент массива в столько битов, сколько мы можем эффективно обработать. Более простой подход - хранить одну десятичную цифру в каждом элементе. Здесь я пошел на компромисс, сохраняя 9 десятичных цифр в каждой uint32_t элемент.

Данные хранятся с прямым порядком байтов, так как гораздо проще vector в конце, когда нам нужны элементы более высокого порядка.

Когда у нас есть этот класс, факториальная функция сама является простотой.

#include <assert.h>
#include <iomanip>
#include <iostream>
#include <stdint.h>
#include <vector>

class Bignum
{
public:
    Bignum(int value)
    {
        assert(value >= 0 && value <= 999999999);
        parts.push_back(value);
    }

    Bignum& operator*=(int rhs)
    {
        assert(rhs >= 0 && rhs <= 999999999);
        uint32_t carry = 0;
        for (size_t i = 0; i < parts.size(); i++)
        {
            uint64_t product = (uint64_t)parts[i] * (uint64_t)rhs + carry;
            parts[i] = (uint32_t)(product % 1000000000LL);
            carry = (uint32_t)(product / 1000000000LL);
        }
        if (carry != 0)
            parts.push_back(carry);
        return *this;
    }

    friend std::ostream & operator<<(std::ostream& stream, const Bignum& num);

private:
    std::vector<uint32_t> parts;
};

inline std::ostream& operator<<(std::ostream& stream, const Bignum& num)
{
    char oldfill = stream.fill('0');
    for (std::vector<uint32_t>::const_reverse_iterator it = num.parts.rbegin(); it != num.parts.rend(); it++)
        stream << *it << std::setw(9);
    stream.fill(oldfill);
    stream.width(0);
    return stream;
}

Bignum factorial(int n)
{
    Bignum fac = 1;
    for (int i = 2; i <= n; i++)
        fac *= i;
    return fac;
}

int main(int argc, char* argv[])
{
    for (int n = 0; n <= 52; n++)
        std::cout << factorial(n) << std::endl;
    return 0;
}

Хорошее решение от Srivatsan Iyer и мое предложение:

  1. Это все еще можно сделать более эффективным с помощью памяти, используя массив unsigned char вместо использования массива int для хранения цифр. Это займет всего 25% памяти, необходимой для массива int.

  2. Для лучшей оптимизации памяти мы также можем использовать один байт для представления двух цифр. Поскольку для представления любой цифры от 0 до 9 достаточно только 4 бита. Таким образом, мы можем упаковать две цифры в один байт, используя побитовые операции. Это займет 12,5% памяти, необходимой для массива int.

Ну, вы должны написать свои собственные математические процедуры с использованием массивов. Это очень просто для сложения, умножение немного сложнее, но все же возможно.

РЕДАКТИРОВАТЬ: хотел опубликовать пример, но пример Шриватсана Айера просто отлично.

У меня есть решение для расчета факториала, который отлично работает по крайней мере для n<=15000. Факториал 10000 можно рассчитать менее чем за 1 секунду, а для расчета факториала требуется менее 2 секунд. (Конечно, ваш вопрос ничего не говорит о временных ограничениях, и эти сроки полностью зависят от машины). В любом случае, концепция довольно проста. Я использую массив символов. Первый символ массива - "1". Младшие биты хранятся в индексе, начиная с 0. Переменная (m согласно моей программе) отслеживает факториальную длину. Конечное значение m - это количество цифр в факториале, а (m-1) -й элемент массива char - это MSB факториала. Когда цикл повторяется, символы добавляются в правой части массива. Переменная 'c' отслеживает перенос.

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

Вы можете проверить мою программу на ideone: http://ideone.com/K410n7

Я считаю, что мое решение все еще можно оптимизировать. Пожалуйста, предложите как.

include<stdio.h> 

char res[200000];

inline int fact(int n)
{

    int i,j;

    register int m,c;

    m=1;

    res[0]='1';

    for(i=2;i<=n;i++)
    {

        c=0;

        for(j=0; j< m; j++){

            c =((res[j]-48)*i)+c;

            res[j]=(c%10)+48;

            c=c/10;

        }
        while(c>0){
            res[m]=(c%10)+48;

            c=c/10;

            m++;

        }

    }

    return m;

}

int main() {


    int n,i,d;

    scanf("%d",&n);


    d=fact(n);

    for(i=d-1;i>=0;i--)

        printf("%c",res[i]);


    return 0;
}

Класс BigInteger решит вашу проблему, а реализация C выше дает вам представление о том, как будет реализован BigInt, за исключением того, что код оптимизирован для скорости и предназначен только для вычисления факториала.

#include <iostream>
using namespace std;
int main ()
{
    int i,n,p=1;
    cout<<"Enter a number: ";
    cin>>n;
    cout<<endl;

    for (i=1;i<=n; i++)
    {
        cout<<i<<" X "; 
        p=p*i;
    }
    cout<<endl<<endl<<p<<" factorial of "<<n;

    return 0;
}

Код показан ниже:

#include<bits/stdc++.h>
using namespace std;
#define MAX 5000

void factorial(int n)
{
    int carry , res_size = 1, res[MAX];
    res[0] = 1;

    for(int x=2; x<=n; x++)
    {
        carry = 0;
        for(int i=0; i<res_size; i++)
        {
          int prod = res[i]*x + carry;
          res[i] = prod % 10;
          carry  = prod/10;
        }
        while (carry)
        {
          res[res_size++] = carry%10;
          carry = carry/10;
        }
     }
     for(int i=res_size-1; i >= 0; i--) 
     {
         cout<<res[i];
     }
}
int main()
{
      int n;
      cin>>n;
      factorial(n);
      cout<<endl;
      return 0;
}

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

Факториал 10 000 000 - это приблизительно 1.2024234127436e+65657059 Это заняло 5,9 секунды. Нахождение точной суммы заняло бы 34 дня.

<?php
$test= 3579;

echo 'Factorial of '.$test.'<br><br>';

$tm= microtime( true);

echo 'Exact '.( $f= factorialexact( $test)).' e+'.(strlen( $f)-1).' missing decimal place after first digit<br>';

echo ( microtime( true) - $tm). ' seconds<br><br>';

$tm= microtime( true);

echo 'Aprox '.factorialapprox( $test).'<br>';

echo ( microtime( true) - $tm). ' seconds<br><br>';


function factorialexact( $n){
    $f= '1';
    for ( $i=$n; $i>1; $i--){
        $f= JL_bcmul( $f, (''.$i));
    }
    return $f;
}

function factorialapprox( $n){
    // Stirling's factorial approximation
    // aprox factorial n = sqrt( 2 * pi * n) * n^n / e^n
    // store in t the easy part, calc the first term easily
    $t= sqrt( 2 * 3.14159265358979 * $n);
    // things get tough from here because for large n
    // n^n can blow away floating point pachages 
    // declare exponent of the number
    $e= 0;
    // the remaining terms are n^n / e^n
    // both n and e (natural log) are raised to the same power
    // that is n, just negative of each other
    for ( $i=0; $i<$n; $i++){
        // loop to 
        // mulitply by n and divide by e for each iteration
        $t= $t * $n / 2.71828182845904;
        // exponents are going to get away from us 
        // so reduce or increase t
        while ( $t>1000){
            $t= $t/1000;
            $e= $e+3;
        } 
        while ( $t<0.001){
            $t= $t*1000;
            $e= $e-3;
        } 
    }
    // garentee the base number is between 1 and 10
    while ( $t>=10){
        $t= $t/10;
        $e= $e+1;
    } 
    while ( $t<1){
        $t= $t*10;
        $e= $e-1;
    } 
    // return at a floating string.
    // do not use parseFloat() or floatval() 
    // $v= explode( 'e', $result); $floatvalue= $v[0] * pow( 10, $v[1]);  
    // won't work either.  $v[1] is way too large
    // the exponent can easily be in the tens of thousands
    $p= '-';
    if ( $e>=0){ $p= '+'; }
    return $t.'e'.$p.$e;
}    

function JL_bcmul( $a, $b){
    if ( function_exists( 'bcmul')){
        return bcmul( ( ''.$a), (''.$b));
    }
    $s= array();
    for ($i=0; $i < count( $a) + count( $b); $i++){ $s[$i]= '0'; }
    $t= 0;
    for ($i=0; $i < strlen( $b); $i++){ 
        for ($j=0; $j < strlen( $a); $j++){
            $t= $s[$i+$j] + intval( $a[strlen( $a) - $j - 1]) * intval( $b[ strlen( $b) - $i - 1]); 
            $s[$i+$j]= $t % 10;
            $s[$i+$j+1]= $s[$i+$j+1] + floor( $t / 10);
        }
    }
    $s= array_reverse( $s);
    return trim( trim(( implode( '', $s).'_'), '0'), '_');
}
#include<stdio.h>
#include<string.h>
char f[10000];
char factorial[1010][10000];
void multiply(int k){
    int ci,sum,i;
    int len = strlen(f);
    ci=0;
    i=0;
    while(i<len){
        sum=ci+(f[i] - '0') * k;
        f[i] = (sum % 10) + '0';
        i++;
        ci = sum/10;
    }
    while(ci>0){
        f[i++] = (ci%10) + '0';
        ci/=10;
    }
    f[i]='\0';
    for(int j=0;j<i;j++)factorial[k][j]=f[j];
    factorial[k][i]='\0';
}
void fac(){
    int k;
    strcpy(f,"1");
    for(k=2;k<=1000;k++)multiply(k);
}
void print(int n){
    int i;
    int len = strlen(factorial[n]);
    printf("%d!\n",n);
    for(i=len-1;i>=0;i--){
        printf("%c",factorial[n][i]);
    }
    printf("\n");
}
int main()
{
    int n;
    factorial[0][0]='1';
    factorial[1][0]='1';
    fac();
    while(scanf("%d",&n)==1){
        print(n);
    }
    return 0;
}

Поскольку все голосовали за Шриватсан, у меня просто есть сомнения, связанные с этой проблемой. Вам нужно хранить все цифры? Если да, то решение Шриватсана в порядке. Если нет, то почему бы просто не отобразить числа, когда вы вычисляете факториал? Я неправильно форматирую вывод, но это может послужить цели.

int factorial(int num)
{
   if (num <= 0)
      return 1;
   else
   {
      std::cout << num << std::endl;
      return num * factorial(num - 1);
   }
}

ОБНОВЛЕНИЕ Для всех downvoters, хотя это 5-летний пост, и выход для factorial(3);

3
2
1
6 // this is the result of the factorial and the digits above are the ones all the digits in the calculation.

Я думал, что это то, что спросил.

Другие вопросы по тегам