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

Объект узла является основным типом данных для всего DOM.

узел А может быть узлом элемента, узлом атрибута, текстовым узлом или любыми другими из типов узлов, объясненных в главе "Типов узлов".

элемент XML - все от (включения) тега запуска элемента к (включению) конечного тэга элемента.

60
задан plasmacel 25 June 2018 в 10:08
поделиться

4 ответа

1. Обнаружение переполнения :

x = a * b;
if (a != 0 && x / a != b) {
    // overflow handling
}

Редактировать: Исправлено деление на 0 (спасибо, Марк!)

2. Вычисление переноса довольно сложно. Один из подходов состоит в том, чтобы разделить оба операнда на полуслова, а затем применить длинное умножение к полусловам:

uint64_t hi(uint64_t x) {
    return x >> 32;
}

uint64_t lo(uint64_t x) {
    return ((1L << 32) - 1) & x;
}

void multiply(uint64_t a, uint64_t b) {
    // actually uint32_t would do, but the casting is annoying
    uint64_t s0, s1, s2, s3; 

    uint64_t x = lo(a) * lo(b);
    s0 = lo(x);

    x = hi(a) * lo(b) + hi(x);
    s1 = lo(x);
    s2 = hi(x);

    x = s1 + lo(a) * hi(b);
    s1 = lo(x);

    x = s2 + hi(a) * hi(b) + hi(x);
    s2 = lo(x);
    s3 = hi(x);

    uint64_t result = s1 << 32 | s0;
    uint64_t carry = s3 << 32 | s2;
}

Чтобы убедиться, что ни одна из частичных сумм не может переполниться, мы рассмотрим худший случай:

        x = s2 + hi(a) * hi(b) + hi(x)

Пусть B = 1 << 32 . Затем у нас есть

            x <= (B - 1) + (B - 1)(B - 1) + (B - 1)
              <= B*B - 1
               < B*B

. Я считаю, что это сработает - по крайней мере, он обрабатывает тестовый пример Sjlver. Кроме того, он не протестирован (и может даже не компилироваться, поскольку у меня под рукой больше нет компилятора C ++).

73
ответ дан 24 November 2019 в 17:35
поделиться

Идея состоит в том, чтобы использовать следующий факт, который верен для интегральной операции:

a * b> c тогда и только тогда, когда a> c / b

/ здесь целочисленное деление.

Псевдокод для проверки переполнения для положительных чисел следующий:

if (a> max_int64 / b) then "overflow" else "ok" .

Для обработки нулей и отрицательных чисел вам следует добавить дополнительные проверки.

Код C для неотрицательных a и b следует:

if (b > 0 && a > 18446744073709551615 / b) {
     // overflow handling
}; else {
    c = a * b;
}

Примечание:

18446744073709551615 == (1<<64)-1

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

Код следующий:

// split input numbers into 32-bit digits
uint64_t a0 = a & ((1LL<<32)-1);
uint64_t a1 = a >> 32;
uint64_t b0 = b & ((1LL<<32)-1);
uint64_t b1 = b >> 32;


// The following 3 lines of code is to calculate the carry of d1
// (d1 - 32-bit second digit of result, and it can be calculated as d1=d11+d12),
// but to avoid overflow.
// Actually rewriting the following 2 lines:
// uint64_t d1 = (a0 * b0 >> 32) + a1 * b0 + a0 * b1;
// uint64_t c1 = d1 >> 32;
uint64_t d11 = a1 * b0 + (a0 * b0 >> 32); 
uint64_t d12 = a0 * b1;
uint64_t c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;

uint64_t d2 = a1 * b1 + c1;
uint64_t carry = d2; // needed carry stored here
32
ответ дан 24 November 2019 в 17:35
поделиться

Версия, которая также работает при a == 0:

    x = a * b;
    if (a != 0 && x / a != b) {
        // overflow handling
    }
9
ответ дан 24 November 2019 в 17:35
поделиться

Если вам нужно не только обнаружить переполнение, но и зафиксировать перенос, лучше всего разбить числа на 32-битные части. Код - кошмар; далее следует лишь набросок:

#include <stdint.h>

uint64_t mul(uint64_t a, uint64_t b) {
  uint32_t ah = a >> 32;
  uint32_t al = a;  // truncates: now a = al + 2**32 * ah
  uint32_t bh = b >> 32;
  uint32_t bl = b;  // truncates: now b = bl + 2**32 * bh
  // a * b = 2**64 * ah * bh + 2**32 * (ah * bl + bh * al) + al * bl
  uint64_t partial = (uint64_t) al * (uint64_t) bl;
  uint64_t mid1    = (uint64_t) ah * (uint64_t) bl;
  uint64_t mid2    = (uint64_t) al * (uint64_t) bh;
  uint64_t carry   = (uint64_t) ah * (uint64_t) bh;
  // add high parts of mid1 and mid2 to carry
  // add low parts of mid1 and mid2 to partial, carrying
  //    any carry bits into carry...
}

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

Если бы мне пришлось делать это по-настоящему, я бы написал процедуру расширенного умножения на местный язык ассемблера. То есть, например, умножьте два 64-битных целых числа, чтобы получить 128-битный результат, который сохраняется в двух 64-битных регистрах. Все разумное оборудование обеспечивает эту функциональность в единственной собственной инструкции умножения - она ​​доступна не только из C.

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

8
ответ дан 24 November 2019 в 17:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: