сложение больших целых чисел без флага переноса

В языках ассемблера обычно есть инструкция, которая добавляет два операнда и перенос. Если вы хотите реализовать сложение больших целых чисел, вы просто добавляете наименьшие целые числа без переноса, а следующие целые числа — с переносом. Как мне сделать это эффективно на C или C++, где у меня нет доступа к флагу переноса? Он должен работать на нескольких компиляторах и архитектурах, поэтому я не могу просто использовать встроенную сборку или что-то в этом роде.


person fredoverflow    schedule 09.06.2012    source источник
comment
Что вы подразумеваете под большими целыми числами? Они похожи на целые числа, которые не подходят ни для одного стандартного типа int?   -  person Tony The Lion    schedule 09.06.2012
comment
@TonyTheLion Подумайте unsigned x[8]; о 256-битном целом числе.   -  person fredoverflow    schedule 09.06.2012
comment
@FredOverflow: вам следует использовать типы uintN_t (например, uint32_t), когда вы имеете в виду определенный размер в битах.   -  person    schedule 09.06.2012
comment
@Hurkyl: ни один компилятор не предлагает uint256_t.   -  person Puppy    schedule 09.06.2012
comment
@DeadMG: Но разные компиляторы предлагают разные размеры для unsigned int.   -  person    schedule 09.06.2012
comment
возможный дубликат Лучший способ обнаружения целочисленного переполнения в C/C++   -  person Oliver Charlesworth    schedule 09.06.2012


Ответы (4)


Вы можете использовать «гвозди» (термин из GMP): вместо того, чтобы использовать все 64 бита uint64_t при представлении числа, используйте только 63 из них с нулевым старшим битом. Таким образом, вы можете обнаружить переполнение с помощью простого битового сдвига. Вы можете даже хотеть меньше чем 63.

Или, вы можете сделать арифметику полуслова. Если вы можете выполнять 64-битные арифметические операции, представьте свое число в виде массива uint32_t (или, что то же самое, разделите 64-битные слова на старшие и младшие 32-битные фрагменты). Затем, выполняя арифметические операции над этими 32-битными целыми числами, вы можете сначала перейти на 64-битные числа, выполнить арифметику там, а затем преобразовать обратно. Это позволяет обнаружить перенос, а также хорошо подходит для умножения, если у вас нет инструкции «умножить привет».

Как показывает другой ответ, вы можете обнаружить переполнение в неподписанном добавлении:

uint64_t sum = a + b;
uint64_t carry = sum < a;

Кроме того, хотя на практике это также будет работать в арифметике со знаком, у вас есть две проблемы:

  • это более сложно
  • Технически переполнение целого числа со знаком является неопределенным поведением.

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

person Community    schedule 09.06.2012
comment
Этот sum < a трюк вообще работает, если sum = a + b + carry? - person fredoverflow; 09.06.2012
comment
@FredOverflow: нет, подумайте, например, a=1, b=max, carry=1. вы получите 1 (+ новый перенос, конечно), но 1 не меньше, чем a. Тем не менее, вы можете разделить его на два дополнения. Затем вы можете просто добавить переносы этих дополнений для безопасной дальнейшей обработки, они будут не более 2 в вашем старшем байте. - person KillianDS; 09.06.2012

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

Другими словами, если a + b меньше a, он переполняется. Конечно, это для положительных значений a и b, но это то, что вы почти наверняка будете использовать для библиотеки bignum.

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

Что-то типа:

carry = 0
for i = 7 to 0:
    if a[i] > b[i]:
        small = b[i], large = a[i]
    else:
        small = a[i], large = b[i]
    if carry is 1 and large is maxvalue:
        c[i] = small
        carry = 1
    else:
        c[i] = large + small + carry
        if c[i] < large:
            carry = 1
        else
            carry = 0

На самом деле вы также можете рассмотреть возможность не использовать все биты в элементах массива.

В прошлом я реализовывал библиотеки, где максимальная «цифра» меньше или равна квадратному корню из самого высокого значения, которое она может содержать. Таким образом, для 8-битных (октетных) цифр вы сохраняете значения от 0 до 15 - таким образом, умножение двух цифр и добавление максимального переноса всегда будут соответствовать октету, что делает обнаружение переполнения спорным, хотя и за счет некоторого хранения.

Точно так же 16-битные цифры будут иметь диапазон от 0 до 255, чтобы не было переполнения на 65536.

На самом деле, я иногда ограничивал его более чем этим, гарантируя, что значение искусственного переноса равно степени десяти (таким образом, октет будет содержать от 0 до 9, 16-битные цифры будут от 0 до 99, 32-битные цифры от 0). до 9999 и так далее.

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

person paxdiablo    schedule 09.06.2012
comment
Ну, если честно, это псевдокод. Я не думаю, что утверждение for верно. Но Python (или, по крайней мере, близок к нему) гениален для псевдокода, если вы держитесь подальше от этих лямбда-выражений, карт и других странных вещей :-) На самом деле я использую подмножество Python для обучение базовым навыкам программирования. - person paxdiablo; 09.06.2012
comment
В вашем коде есть ошибка: если b[i] имеет максимальное значение, а carry равно 1, то c[i] == a[i]. - person ; 09.06.2012
comment
Ой. Также тот факт, что вы нигде явно не писали операцию по модулю, поэтому c[i] < a[i] невозможно, если только эти типы каким-то образом не переполняются и не перекрываются. - person Potatoswatter; 09.06.2012
comment
@Potatoswatter, это именно то, о чем задается вопрос (C-подобные unsigned целые числа). - person huon; 09.06.2012
comment
Hurkyl, хороший улов, исправил ошибку. С другой стороны, dbaupp прав. Обтекание заложено в типе данных, его не нужно делать явно. - person paxdiablo; 09.06.2012

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

просто начните с переноса 0.

person Cheers and hth. - Alf    schedule 09.06.2012
comment
Подойдет любой операнд — он работает только для двоичного (2-операндного) сложения. Так что любой операнд подойдет. - person Potatoswatter; 09.06.2012
comment
анализ. для первого сложения максимальный результат равен 2*(2^n-1) = 2^(n+1)-2, что означает, что старший бит, который можно установить, равен n, что означает, что максимальный перенос равен 1. для следующего добавления 2* (2^n-1) + 1 = 2^(n+1)-1, то же самое. и так далее. - person Cheers and hth. - Alf; 09.06.2012
comment
Хм, все добавления (кроме первого) будут иметь три операнда (a + b + перенос). Моя интуиция подсказывает мне, что есть несколько скрытых случаев. - person fredoverflow; 09.06.2012
comment
@Fred: вот что такое + 1 в анализе. - person Cheers and hth. - Alf; 09.06.2012

Если я вас правильно понял, вы хотите написать собственное дополнение для своего большого целочисленного типа.

Вы можете сделать это с помощью простой функции. Не нужно беспокоиться о флаге переноса при первом запуске. Просто идите справа налево, добавляйте цифру за цифрой и флаг переноса (внутри этой функции), начиная с переноса 0, и присваивайте результату (a+b+carry) %10 и переносу значение (a+ б+перенос) / 10.

этот SO может иметь значение: как реализовать большой int в c

person Mare Infinitus    schedule 09.06.2012
comment
хорошо, не прочитал это в вопросе. простите за это. но ссылка показывает базовую реализацию x - person Mare Infinitus; 09.06.2012
comment
@MareInfinitus Эти вопросы и ответы - упражнение по программированию. Это как суррогат для языка ассемблера. - person Potatoswatter; 09.06.2012
comment
что ты имеешь в виду? ОП хочет сделать домашнее задание? почему бы нам не помочь ему? - person Mare Infinitus; 09.06.2012