Программа высокой точности, которая вычисляет 2 ^ n

Я создаю программу на C, которая может получить степень 2. Пользователь вводит значение n, и программа вычисляет 2^n.

Вот код.

Проблема возникает, когда я ввожу 100

Что я получаю:

1,267,650,600,228,229,400,000,000,000,000

Что я должен получить

1,267,650,600,228,229,401,496,703,205,376

Он должен быть полностью написан на ANSI C. Есть идеи, как повысить точность? Максимальное значение N должно быть 256 (256 бит, я полагаю, что означает, что максимальный выход должен быть 2^256).

Чего мне здесь не хватает, так это точности, и я не знаю, как это исправить. Любые идеи?


person Icekilla    schedule 01.11.2012    source источник
comment
см. также stackoverflow.com/questions/3340511/   -  person lqs    schedule 01.11.2012
comment
Тип double имеет только то количество значащих цифр, больше не может.   -  person jondinham    schedule 01.11.2012
comment
Bignum на C и C ++ должен быть одним из наиболее часто задаваемых вопросов, у которых есть трудное решение.   -  person Mysticial    schedule 01.11.2012
comment
Всего один комментарий: я НЕ должен использовать внешние библиотеки. Это должен быть ANSI C. Я готовлюсь к соревнованиям ACM, поэтому :(   -  person Icekilla    schedule 01.11.2012
comment
Как указывает @Mysticial, вы ищете bignum решение. Информацию о том, что это такое, можно найти в википедии: en.wikipedia.org/wiki/Arbitrary-precision_arithmetic   -  person lnafziger    schedule 01.11.2012
comment
даже тип long double (как на моем GCC 44) - это только 96-битный тип, он имеет не более 28 значащих цифр   -  person jondinham    schedule 01.11.2012
comment
@Mysticial, вы ошибаетесь, говоря, что это чрезвычайно сложная проблема. Реализация сложения произвольной точности - не самая сложная вещь в мире.   -  person MK.    schedule 01.11.2012
comment
Тем более что это всего на 256 бит ....   -  person lnafziger    schedule 01.11.2012
comment
@MK. да, если честно, это была проблема в моей книге по программированию в средней школе   -  person jondinham    schedule 01.11.2012
comment
Было бы обманом жестко закодировать 257 возможных выходных данных в массив и просто проиндексировать по N?   -  person Kevin    schedule 01.11.2012
comment
@Kevin Это не обман, это разумно. Что неразумно, так это вся эта чушь о сложности и [long] double типах, которые не подходят для этой целочисленной задачи.   -  person Jim Balter    schedule 01.11.2012
comment
@JimBalter Хорошо, моя вина. Я упустил из виду простоту этой конкретной задачи, поскольку большое умножение не требуется. (и производительность не имеет значения) Сложная часть, о которой я говорил, - это общий случай с большим умножением / делением. Базовая реализация большого умножения не так уж и сложна, но деление раздражает. Достижение субквадратичной и квазилинейной производительности - это большая банка червей, которую я не буду раскрывать.   -  person Mysticial    schedule 01.11.2012
comment
возможный дубликат больших чисел типа double / float / numbers   -  person finnw    schedule 01.11.2012
comment
@Mysticial Я понимаю все, что касается общей сложности и раздражения ... см. Мои комментарии под (и отрицательным) ответом Дейва, в котором говорится о простоте и удобстве бинарных операций над степенями двойки (которые здесь даже не актуальны потому что это просто вопрос размещения одного бита в правильном слове), полностью игнорируя сложность деления MP, беззаботно ссылаясь, без объяснения, на то, что делает аппаратный делитель.   -  person Jim Balter    schedule 02.11.2012


Ответы (6)


Вот моя быстрая и грязная реализация подхода Хаммара., где десятичное число хранится в виде строки C с цифрами в обратном порядке .

Запустите код на ideone

void doubleDecimal(char * decimal)
{
    char buffer[256] = "";
    char c;
    unsigned char d, carry = 0;
    int i = 0;

    while (c = decimal[i])
    {
        d = 2 * (c - '0') + carry;
        buffer[i] = (d % 10) + '0';
        carry = d / 10;
        i++;
    }

    if (carry > 0)
        buffer[i++] = (carry % 10) + '0';

    buffer[i] = '\0';
    strncpy(decimal, buffer, 256);
}

void reverse(char * str)
{
    int i = 0;
    int j = strlen(str) - 1;

    while (j > i)
    {
        char tmp = str[i];
        str[i] = str[j];
        str[j] = tmp;

        i++;
        j--;
    }
}

int main(void)
{
    char decimal[256] = "1";
    int i;

    for (i = 0; i < 100; i++)
        doubleDecimal(decimal);

    reverse(decimal);
    printf("%s", decimal);

    return 0;
}

Вывод:

1267650600228229401496703205376
person Brian L    schedule 01.11.2012
comment
strncpy почти всегда является неправильным инструментом (он здесь), и его никогда не следует предлагать новичкам. - person Jim Balter; 01.11.2012
comment
@Jim, возможно, вы могли бы предложить правильную функцию для копирования одной строки в другую? - person Brian L; 01.11.2012
comment
strcpy здесь в порядке ... если вы сохранили более 256 байт в буфере, у вас уже есть неопределенное поведение. Как правило, если вам нужны безопасные копии строк и вы хотите использовать стандартную процедуру библиотеки C, snprintf работает. Лично у меня есть собственная библиотека, которая устраняет этот недостаток. Но strncpy неправильно неправильно неправильно, потому что а) он бессмысленно заполняется NUL, и это может быть обнаружимо дорогостоящим с большими массивами и циклами, и б) он не завершает результат NUL. - person Jim Balter; 01.11.2012
comment
@ Джим, спасибо, ты побудил меня провести небольшое исследование поведения strncpy. Вы правильно сказали, что strcpy подходит для диапазона целых чисел, с которым мы имеем дело (максимум 2 ^ 256). - person Brian L; 01.11.2012
comment
Что случилось подарить мужчине рыбу. Неужели это действительно хорошая идея сделать за него домашнее задание? - person Cheers and hth. - Alf; 02.11.2012

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

Если у вас есть массив из 10 цифр 1, вам нужно только реализовать сложение по основанию 10 с переносом, чтобы иметь возможность умножать на 2 (путем добавления числа к самому себе). Сделайте это n раз в цикле, и вы получите ответ.

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

1 Совет: удобнее хранить цифры в обратном порядке.

person hammar    schedule 01.11.2012
comment
Я рад, что кто-то дал разумный ответ. Удивительно, как немногие люди могут ясно мыслить о решении проблем ... они знают о мелочах и не могут мыслить дальше. - person Jim Balter; 01.11.2012
comment
нумерация с прямым порядком байтов по основанию 10, мне это нравится! - person dave; 01.11.2012
comment
@annoying_squid: Нет, в любом случае вся работа - это базовое преобразование. - person R.. GitHub STOP HELPING ICE; 01.11.2012
comment
Кстати, умножать на 2 на каждом шаге довольно неэффективно. Я бы умножил на 536870912 на каждом шаге (наибольшая степень 2 меньше, чем наибольшая степень 10, которая подходит для 32-битного целого числа) и буду работать с основанием 1000000000, а не с основанием 10. - person R.. GitHub STOP HELPING ICE; 01.11.2012

double - это (вероятно) 64-битное значение. Вы не можете хранить 256 бит точности в 64 битах. Причина, по которой вы получаете близкое число, заключается в том, что числа с плавающей запятой хранятся с разной точностью - не все последовательные числа могут быть представлены, но вы можете представлять очень большие числа. В данном случае бесполезно. Вы хотите либо использовать библиотеку произвольной точности, либо, поскольку это, вероятно, домашнее задание, вы должны написать свою собственную.

person MK.    schedule 01.11.2012
comment
Как насчет использования массивов? Проблема в том, чтобы правильно их реализовать. - person Icekilla; 01.11.2012
comment
@Icekilla да, вы можете делать это с массивами. Это будет сворачивание вашей собственной библиотеки bigint. - person MK.; 01.11.2012
comment
@ MK.Да ладно. Школьник Eavery научился складывать вручную. Все, что здесь требуется, - это многократное удвоение числа ... сделайте это с помощью массива символов ASCII. - person Jim Balter; 01.11.2012
comment
@JimBalter - это именно то, что делает библиотека bigint. - person MK.; 01.11.2012
comment
@MK. Нет, это не так. Во-первых, помимо удвоения он выполняет множество других операций. Во-вторых, ни одна достойная библиотека bigint не использует символьные массивы цифр ASCII. - person Jim Balter; 01.11.2012
comment
На самом деле, это не должны быть символы ASCII, потому что их сложнее добавить. Это должны быть байты, содержащие значения от 0 до 9. Это решение с базой 10, тогда как библиотеки MP обычно используют базу 10 ^ n для наибольшей такой базы, которая соответствует int (или другому быстрому собственному целочисленному типу). - person Jim Balter; 01.11.2012
comment
Байты или цифры ASCII должны работать примерно одинаково. Вычитание смещения '0' при добавлении добавляет примерно 0-5% времени к времени выполнения (вероятно, 0 для арок, таких как x86, с составными арифметическими инструкциями). - person R.. GitHub STOP HELPING ICE; 01.11.2012

Типичный double, использующий 64-битный IEEE 754, имеет точность около 51 бита, IIRC.

Скорее всего, смысл поддержки показателей до 256 состоит в том, чтобы превысить эту точность, а также точность long double или long long, чтобы вам приходилось делать что-то самостоятельно.

Тогда в качестве домашнего упражнения

  • Сохранение значений десятичных цифр в массиве + количество цифр
  • Реализовать удвоение значения в таком массиве + счетчик
  • Начните с 1 и удвойте значение соответствующее количество раз.
person Cheers and hth. - Alf    schedule 01.11.2012

Несколько вещей, о которых вам стоит подумать, чтобы решить эту проблему:

  1. Вы имеете дело только с целыми числами, поэтому вы должны использовать целочисленное представление (вам нужно будет свернуть свое собственное, потому что вы не можете использовать long long, который имеет длину "всего" 64 бита).
  2. Вы говорите, что степень двойки - как удобно - компьютеры хранят числа, используя степень двойки (вам нужно будет использовать только операции сдвига и возиться с битами ... никакого умножения не потребуется).
  3. Как вы можете преобразовать число с основанием 2 в число с основанием 10 для отображения (подумайте о делении и выводе одного числа за раз (подумайте о том, что делает аппаратный делитель, чтобы получить правильные манипуляции с битами).
person dave    schedule 01.11.2012
comment
заботиться о разработке? Как волшебным образом преобразовать двоичное в десятичное? - person MK.; 01.11.2012
comment
волшебное преобразование из двоичного в десятичное не так уж и сложно (ладно, нам, возможно, придется реализовать некоторые арифметические операторы для нашего типа); это задание на первый год в университете. - person dave; 01.11.2012
comment
Дэйв, ты совершенно упустил суть. Для преобразования в десятичное требуется деление и mod 10 вашего многословного двоичного числа ... двоичное число, которое само по себе бессмысленно, потому что оно содержит только один 1 бит. Да это не волшебство, но преобразование - это целая проблема. - person Jim Balter; 01.11.2012
comment
@JimBalter: как я упустил суть, взгляните на шаг 3 - подумайте о разделении оборудования. Это может быть сделано. Я не пытался решить проблему за него, просто давая ему возможность найти решение (это лучше, чем использовать double, но не так хорошо, как у hammar). - person dave; 01.11.2012
comment
Я объяснил, почему вы полностью упускаете суть. Это можно сделать - я просто СКАЗАЛ, что это не волшебство, не так ли? это лучше, чем использовать double - да, потому что использование double вообще не работает, но это совершенно неправильный подход. - person Jim Balter; 01.11.2012
comment
P.S. Взгляните на stackoverflow.com / questions / 1686004 / ... обратите внимание на ответ. - person Jim Balter; 01.11.2012
comment
@lnafziger Почему символы лучше, чем целые? - person potrzebie; 01.11.2012
comment
@potrzebie Потому что вы можете хранить произвольное количество битов в одном массиве символов с завершающим нулем, на который вы можете ссылаться с помощью указателя. Есть много функций, которые уже работают подобным образом, и вам не нужно отслеживать размер массива. - person lnafziger; 01.11.2012
comment
В этом конкретном случае, если вам не нужно беспокоиться об использовании каждого бита (будет некоторая потеря памяти), вы даже можете сохранить числа, которые вы храните таким образом, непосредственно читаемыми. I.E. число 1,234,567,890 будет сохранено как массив символов из 1234567890, и ваши математические процедуры будут работать с отдельными символами, а не с байтами. Теперь вы можете сложить два числа вместе, используя базовые математические методы начальной школы (особенно при умножении на однозначное число, например 2). - person lnafziger; 01.11.2012

Вы не можете хранить 256 бит точности в 64 битах. Причина, по которой вы получаете закрывающееся число, заключается в том, что числа с плавающей запятой хранятся с разной точностью. Для всех могут быть представлены порядковые числа, но вы можете представлять очень большие числа. В данном случае бесполезно.

person Chirag Shivalker    schedule 01.11.2012