2015-06-25 4 views
3

Я пытаюсь вычислить комбинацию C (40, 20) в C++, однако типы данных в C++, похоже, не могут правильно обрабатывать этот расчет, даже если я использовал тип данных long long. Ниже приведен мой код:Переполнение при вычислении комбинаций

#include <iostream> 

long long fac(int x) { 
    register long long i,f = 1; // Optimize with regFunction 
    for(i = 1;i <= x;i++) 
     f *= i; 
    std::cout << f << std::endl; 
    return f; 
} 

// C(n,r) = n!/r!(n-r)! 
long long C(long long n, long long r) { 
    return fac(n)/(fac(r) * fac(n - r)); 
} 

int main(int argc, char const *argv[]) { 
    std::cout << C(40, 20) << std::endl; 
    return 0; 
} 

Любая идея для решения этой проблемы?

+1

Ummm .... разве вы не имеете в виду 'C (40, 20)'? Напротив, это не имеет смысла. Как вы можете выбрать 40 образцов из группы из 20? – CoryKramer

+0

40! >> LLONG_MAX –

+1

1. Найдите способ выполнить вычисления с использованием меньших значений, ИЛИ 2. Найдите способ вычисления с меньшей точностью и используйте 'double', OR 3. используйте библиотеку bignum. – tenfour

ответ

4

Compute C сразу, немедленно выполняя деление после умножения:

long long C(long long n, long long r) 
{ 
    long long f = 1; // Optimize with regFunction 
    for(auto i = 0; i < r;i++) 
     f = (f * (n - i))/(i + 1); 
    return f ; 
} 

Результат должен быть точным (деление без остатков, до перелива), так как любое целое число раз, присутствующего в (я + 1) уже присутствует в (n -i).(Не должно быть слишком сложно доказать)

0

Даже если вы использовали uint64 aka ulonglong, максимальное значение равно 18446744073709551615, тогда как 40! 815915283247897734345611269596115894272000000000, который является немного больше.

Я рекомендую вам использовать GMP для такого рода математики

2

Проблемы в том, что факториалы получить большие очень быстро. 40! слишком велика для хранения в long long. К счастью, вам действительно не нужно вычислять этот номер здесь, так как вы можете уменьшить долю в вычислении C(n, r) перед ее вычислением. Это дает уравнение (от Wikipedia):

reduced combination formula

Это работает намного лучше, так как K! ( r! В вашем коде) намного меньше, чем n!. Однако в какой-то момент он также сломается.

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

+0

Это написано, если 'k <21' –

+0

@Thomas True; Я добавил небольшое разъяснение. –

2

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

После того, как вы правильно установили его (имеется в репозиториях на большинстве дистрибутивов Linux), сводится к тому:

  • добавления #include <gmpxx.h> в исходный файл
  • заменяющего long long с mpz_class
  • компиляции с -lgmpxx -lgmp

Источник:

#include <iostream> 
#include <gmpxx.h> 

mpz_class fac(mpz_class x) { 
    int i; 
    mpz_class f(1); // Optimize with regFunction 
    for(i = 1;i <= x;i++) 
     f *= i; 
    std::cout << f << std::endl; 
    return f; 
} 

// C(n,r) = n!/r!(n-r)! 
mpz_class C(mpz_class n, mpz_class r) { 
    return fac(n)/(fac(r) * fac(n - r)); 
} 

int main(int argc, char const *argv[]) { 
    std::cout << C(40, 20) << std::endl; 
    return 0; 
} 

Компиляция и запуск:

$ g++ comb.cpp -lgmpxx -lgmp -o comb 
$ ./comb 
2432902008176640000 
2432902008176640000 
815915283247897734345611269596115894272000000000 
137846528820 

Если вы хотите быть уверенными, Вы можете сделать гораздо больше, но это поможет вам ответы.

3

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

Вы могли бы хотеть проверить эти:

https://mattmccutchen.net/bigint/

https://gmplib.org/

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

+0

Использование строк для хранения чисел - это библиотека бигума бедного человека. Он не имеет преимуществ перед использованием более совершенных библиотек. –

+0

Да, это может быть правдой. Я не уверен в этой части, как я уже упоминал. Но я отредактирую его, чтобы сделать его более понятным. – ralzaul

Смежные вопросы