Эффективное возведение в степень для ОГРОМНЫХ чисел (я говорю о гуголах)

Я нахожусь в процессе решения простой комбинированной задачи, решение которой равно 2^(n-1).

Единственная проблема: 1 ‹= n ‹= 2^31 -1 (максимальное значение для 32-битного целого числа со знаком)

Я пытался использовать класс Java BigInteger, но он истекает для чисел 2 ^ 31/10 ^ 4 и выше, так что это явно не сработает.

Кроме того, я ограничен использованием только встроенных классов для Java или C++.

Зная, что мне нужна скорость, я решил создать класс на C++, который выполняет арифметические операции со строками.

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

Но даже при этом я не могу умножить 2 на 2 ^ 31 - 1 раз, это просто недостаточно эффективно.

Итак, я начал читать тексты по проблеме и пришел к решению...

2^n = 2^(n/2) * 2^(n/2) * 2^(n%2) (где / обозначает целочисленное деление, а % обозначает модуль)

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

Если у кого-то есть какие-либо знания о том, как решить эту проблему, пожалуйста, уточните (пример кода приветствуется).

ОБНОВЛЕНИЕ

Спасибо всем за вашу помощь! Ясно, что эта проблема должна решаться реалистичным способом, но мне удалось превзойти java.math.BigInteger с помощью мощной функции, которая выполняет только ceil(log2(n)) итерации.

Если кому-то интересен код, который я создал, вот он...

using namespace std;

bool m_greater_or_equal (string & a, string & b){ //is a greater than or equal to b?
    if (a.length()!=b.length()){
        return a.length()>b.length();
    }
    for (int i = 0;i<a.length();i++){
        if (a[i]!=b[i]){
            return a[i]>b[i];
        }
    }
    return true;
}

string add (string& a, string& b){
    if (!m_greater_or_equal(a,b)) return add(b,a);
    string x = string(a.rbegin(),a.rend());
    string y = string(b.rbegin(),b.rend());
    string result = "";
for (int i = 0;i<x.length()-y.length()+1;i++){
    y.push_back('0');
}

int carry = 0;
for (int i =0;i<x.length();i++){
    char c = x[i]+y[i]+carry-'0'-'0';
    carry = c/10;
    c%=10;
    result.push_back(c+'0');
}
if (carry==1) result.push_back('1');
return string(result.rbegin(),result.rend());

}

string multiply (string&a, string&b){
    string row = b, tmp;
    string result = "0";

    for (int i = a.length()-1;i>=0;i--){

        for (int j= 0;j<(a[i]-'0');j++){
            tmp = add(result,row);
            result = tmp;
        }
        row.push_back('0');
    }
    return result;
}

int counter = 0;

string m_pow (string&a, int exp){
    counter++;
    if(exp==1){
        return a;
    }
    if (exp==0){
        return "1";
    }
    string p = m_pow(a,exp/2);
    string res;
    if (exp%2==0){
        res = "1";  //a^exp%2 is a^0 = 1
    } else {
        res = a;   //a^exp%2 is a^1 = a
    }
    string x = multiply(p,p);
    return multiply(x,res);
    //return multiply(multiply(p,p),res); Doesn't work because multiply(p,p) is not const

}

int main(){


    string x ="2";

    cout<<m_pow(x,5000)<<endl<<endl;
    cout<<counter<<endl;

    return 0;
}

person Jimmy Huch    schedule 07.01.2012    source источник
comment
В C++ нет встроенных классов больших чисел. Вы все еще хотите рассмотреть популярные библиотеки или хотите исключить С++ из вопроса? Я не думаю, что кто-то поможет вам создать собственную библиотеку bignum здесь, когда уже существуют отличные библиотеки (GMP).   -  person Kerrek SB    schedule 07.01.2012
comment
Зная, что мне нужна скорость, я решил создать класс на C++, который выполняет арифметические операции со строками. Предполагая, что вы имеете в виду то же самое, что и я, когда говорю о строках (это что-то построенное из символов), которые действительно не сильно улучшат вашу производительность (обычно берут за основу максимально возможный тип). Кроме того, правильно ли я вас понимаю, что вам нужны числа в порядке 2 ^ (2 ^ 31)? В этом случае вам действительно нужна вся скорость, которую вы можете получить, поэтому я бы не советовал писать такой класс самостоятельно.   -  person Grizzly    schedule 07.01.2012
comment
Если вы хотите получить ответ в двоичном формате, вам не нужно выполнять никаких вычислений (просто напишите 1, а затем n - 1 нули - как указано в ответе Оли). Но если вы хотите напечатать ответ в десятичном виде... ну... Не рассчитывайте на то, что BigInteger в Java сделает это... И если вы говорите о размерах, значительно превышающих n > 2^32, не рассчитывайте, что GMP сделает это это либо ... У вас закончится память, прежде чем это произойдет ...   -  person Mysticial    schedule 07.01.2012
comment
Да, я просто не подумал о битовой арифметике, это, вероятно, был бы лучший способ, хотя рекурсивный подход, который предлагает dasblinkenlight, также довольно интересен, и я тоже его попробую.   -  person Jimmy Huch    schedule 07.01.2012
comment
Просто примечание об использовании std::string: если вы используете std::vector‹unsigned long› для представления (с дополнительным битом для указания знака) и обязательно используете unsigned long long для вычислений, вы должны быть в состоянии для дальнейшего повышения производительности, в основном используя тот же подход.   -  person Dietmar Kühl    schedule 08.01.2012
comment
@Dietmar Я сомневаюсь, что это улучшит производительность, поскольку я сохраняю только 1 цифру в каждом индексе. Если бы я сохранял однозначные числа в unsigned long, это было бы пустой тратой памяти, потому что sizeof(char) = 1, а sizeof(long) = 8   -  person Jimmy Huch    schedule 08.01.2012
comment
@JimmyHuch: ну да. Однако, если вы будете работать руками с большим количеством пальцев, например. с 1000000000 пальцев ваш подход остается в силе, и вы все равно можете форматировать число достаточно просто.   -  person Dietmar Kühl    schedule 08.01.2012
comment
@JimmyHuch Вы должны исследовать свои алгоритмы, 2^20 = 1048576 ? 1040576 - это первая степень числа 2, для которой ваш код возвращает неправильные результаты, но с этого момента он неверен, по крайней мере, до 32 (дальше не проверял).   -  person Daniel Fischer    schedule 08.01.2012


Ответы (3)


Самый простой способ применить этот метод в вашем коде — применить его самым прямым способом — рекурсивно. Это работает для любого числа a, а не только для 2, поэтому я написал код, который принимает a в качестве параметра, чтобы сделать его более интересным:

MyBigInt pow(MyBigInt a, int p) {
    if (!p) return MyBigInt.One;
    MyBigInt halfPower = pow(a, p/2);
    MyBigInt res = (p%2 == 0) ? MyBigInt.One : a;
    return res * halfPower * halfPower;
}
person Sergey Kalinichenko    schedule 07.01.2012
comment
Похоже, это будет повторяться бесконечно. - person Kerrek SB; 07.01.2012
comment
@kerreksb К сожалению, вы правы! Я пропустил базовый случай (сейчас исправлено). Спасибо! - person Sergey Kalinichenko; 07.01.2012

Как уже упоминалось в ответе @Oli, это не вопрос вычисления 2^n, поскольку это просто 1, за которым следует 0 в двоичном формате.

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

Мой ответ на это таков: это нереалистично (надеюсь, этот вопрос возник просто из любопытства).

Вы упомянули о попытке вычислить 2^(2^31 - 1) и распечатать его в десятичном виде. Это число состоит из 646 456 993 цифр.

  • Java BigInteger не может этого сделать. Он предназначен для небольших чисел и использует алгоритмы O(n^2).
  • Как упоминалось в комментариях, в C++ нет встроенных библиотек BigNum.
  • Даже Mathematica с этим не справится: General::ovfl : Overflow occurred in computation.
  • Лучше всего использовать библиотеку GMP.

Если вам просто интересно увидеть часть ответа:

2^(2^31 - 1) = 2^2147483647 = 

880806525841981676603746574895920 ... 7925005662562914027527972323328

(total: 646,456,993 digits)

Это было сделано с использованием библиотеки с закрытым исходным кодом и заняло примерно 37 секунд и 3,2 ГБ памяти на Core i7 2600K @ 4,4 ГГц, включая время, необходимое для записи всех 646 миллионов цифр в массивный текстовый файл.
(Потребовался блокнот) дольше, чтобы открыть файл, чем необходимо для его вычисления.)


Теперь, чтобы ответить на ваш вопрос о том, как на самом деле вычислить такую ​​мощность в общем случае, у @dasblinkenlight есть ответ на тот, который является вариантом Возведение в степень путем возведения в квадрат.

Преобразование из двоичного в десятичное для больших чисел является гораздо более сложной задачей. Стандартный алгоритм здесь — преобразование по принципу "разделяй и властвуй". .

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

person Mysticial    schedule 07.01.2012
comment
Алгоритм, опубликованный dasblinkenlight, представляет собой один из вариантов возведения в степень путем возведения в квадрат, это предложение сформулировано неправильно. - person Daniel Fischer; 07.01.2012
comment
Вы правы, я проглядел halfPower * halfPower в его ответе. Обновление... - person Mysticial; 07.01.2012

Вам вообще не нужно делать никакого умножения. 2^(n-1) — это просто 1 << (n-1), т. е. 1, за которой следуют (n-1) нуля (в двоичном формате).

person Oliver Charlesworth    schedule 07.01.2012
comment
Все еще нет способа сохранить 1 ‹‹ 2147483647 даже в 128-битном целом числе - person Jimmy Huch; 07.01.2012
comment
О, я вижу, если бы число хранилось в строке двоичных цифр, а затем преобразовывалось бы в десятичное, это могло бы сработать! - person Jimmy Huch; 07.01.2012
comment
@JimmyHuch: Да, конечно, вам нужно будет придумать подходящий способ хранения этого числа. Но это не тот вопрос, который вы задали! - person Oliver Charlesworth; 07.01.2012
comment
Не беспокойтесь, я думаю, я могу понять это. - person Jimmy Huch; 07.01.2012
comment
Хорошо, мне нужно выразить ответ в десятичном виде, поэтому преобразование представляет собой проблему, потому что оно все еще требует от меня выполнения арифметических действий 2^(n-1) для применения преобразования, что возвращает нас к основной проблеме. Это не сработает, если не будет более эффективного способа преобразования двоичного кода в десятичный (в чем я сомневаюсь). - person Jimmy Huch; 07.01.2012
comment
@Jimmy: Вы хотите отобразить число с 2 ^ 31 двоичной цифрой? (это примерно 670 миллионов десятичных цифр.) - person Oliver Charlesworth; 07.01.2012
comment
Вот почему я должен использовать десятичную дробь;) - person Jimmy Huch; 07.01.2012
comment
@JimmyHuch: Зачем тебе это? - person Oliver Charlesworth; 07.01.2012
comment
1 ‹‹ 2147483647 - цифры даже не так высоки. - person Don Reba; 07.01.2012
comment
Если вам просто нужен ответ: 2^2147483647 = 88080652584198167660374 ... 14027527972323328 (длина 646 456 993 цифр). - person Mysticial; 07.01.2012
comment
Мне нужно сделать это, чтобы овладеть искусством программирования - person Jimmy Huch; 07.01.2012
comment
@ Джимми, обратите внимание, что у 32-битного процесса недостаточно памяти для хранения этих значений в виде символов двоичных цифр. Вам придется использовать фактический двоичный файл. Символы десятичных цифр составляют более половины гигабайта данных. - person Mooing Duck; 07.01.2012