Как избежать переполнения в expr. A * B - C * D

Мне нужно вычислить выражение, которое выглядит так: A*B - C*D, где их типы: signed long long int A, B, C, D; Каждый номер может быть действительно большим (не переполняя его тип). В то время как A*B может вызвать переполнение, в то же время выражение A*B - C*D может быть очень маленьким. Как я могу правильно вычислить его?

Например: MAX * MAX - (MAX - 1) * (MAX + 1) == 1, где MAX = LLONG_MAX - n и n - некоторое натуральное число.

Ответ 1

Это кажется слишком тривиальным, я думаю. Но A*B - это тот, который может переполняться.

Вы можете сделать следующее, не теряя точности

A*B - C*D = A(D+E) - (A+F)D
          = AD + AE - AD - DF
          = AE - DF
             ^smaller quantities E & F

E = B - D (hence, far smaller than B)
F = C - A (hence, far smaller than C)

Это разложение может быть выполнено дальше.
Как отметил @Gian, во время операции вычитания может потребоваться осторожность, если тип unsigned long long.


Например, в случае, которое у вас есть в вопросе, требуется только одна итерация,

 MAX * MAX - (MAX - 1) * (MAX + 1)
  A     B       C           D

E = B - D = -1
F = C - A = -1

AE - DF = {MAX * -1} - {(MAX + 1) * -1} = -MAX + MAX + 1 = 1

Ответ 2

Простейшим и наиболее общим решением является использование представления, которое не может переполняться, либо с использованием длинной целочисленной библиотеки (например, http://gmplib.org/), либо представляющей использование структуры или массива и реализации своего рода длинного умножения (т.е. разделения каждого числа на две 32-разрядные половинки и выполнения умножения, как показано ниже:

(R1 + R2 * 2^32 + R3 * 2^64 + R4 * 2^96) = R = A*B = (A1 + A2 * 2^32) * (B1 + B2 * 2^32) 
R1 = (A1*B1) % 2^32
R2 = ((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) % 2^32
R3 = (((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) %2^32
R4 = ((((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) / 2^32) + (A2*B2) / 2^32

Предполагая, что конечный результат соответствует 64 бит, на самом деле вам действительно не нужны большинство бит R3 и ни один из R4

Ответ 3

Обратите внимание, что это не является стандартным, так как он полагается на обходной поток с пересылкой. (GCC имеет флаги компилятора, которые позволяют это.)

Но если вы просто выполняете все вычисления в long long, результат применения формулы напрямую:
(A * B - C * D) будет точным, если правильный результат вписывается в long long.


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

(long long)((unsigned long long)A * B - (unsigned long long)C * D)

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


Если вам нужно больше педантичного решения, я думаю, вы должны использовать "длинную арифметику"

Ответ 4

Это должно работать (я думаю):

signed long long int a = 0x7ffffffffffffffd;
signed long long int b = 0x7ffffffffffffffd;
signed long long int c = 0x7ffffffffffffffc;
signed long long int d = 0x7ffffffffffffffe;
signed long long int bd = b / d;
signed long long int bdmod = b % d;
signed long long int ca = c / a;
signed long long int camod = c % a;
signed long long int x = (bd - ca) * a * d - (camod * d - bdmod * a);

Здесь мой вывод:

x = a * b - c * d
x / (a * d) = (a * b - c * d) / (a * d)
x / (a * d) = b / d - c / a

now, the integer/mod stuff:
x / (a * d) = (b / d + ( b % d ) / d) - (c / a + ( c % a ) / a )
x / (a * d) = (b / d - c / a) - ( ( c % a ) / a - ( b % d ) / d)
x = (b / d - c / a) * a * d - ( ( c % a ) * d - ( b % d ) * a)

Ответ 5

Вы могли бы вычислить наибольший общий коэффициент для всех ваших значений и затем делить их на этот коэффициент, прежде чем выполнять свои арифметические операции, а затем снова умножить. Это предполагает, что такой фактор существует, однако (например, если A, B, C и D оказываются взаимно простыми, у них не будет общего фактора).

Аналогично, вы можете подумать о работе над лог-шкалами, но это будет немного страшно, с учетом числовой точности.

Ответ 6

Если результат соответствует длинному длинному int, тогда выражение A * B-C * D в порядке, так как оно выполняет арифметический mod 2 ^ 64 и даст правильный результат. Проблема состоит в том, чтобы знать, соответствует ли результат длинному int int. Чтобы обнаружить это, вы можете использовать следующий трюк, используя удвоения:

if( abs( (double)A*B - (double)C*D ) > MAX_LLONG ) 
    Overflow
else 
    return A*B-C*D;

Проблема с этим подходом заключается в том, что вы ограничены точностью мантиссы удвоений (54 бит?), поэтому вам нужно ограничить продукты A * B и C * D до 63 + 54 бит (или, вероятно, немного Меньше).

Ответ 7

E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

затем

A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1

Ответ 8

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

Число 123 может быть выражено как:

123 = 100 * 1 + 10 * 2 + 3

для которого вы просто создаете массив [1 2 3].

Вы делаете это для всех чисел A, B, C и D, а затем умножаете их на многочлены. После того, как вы получите результирующий многочлен, вы просто восстановите его число.

Ответ 9

Пока signed long long int не будет содержать A*B, два из них будут. Таким образом, A*B можно разложить на древообразные члены различного показателя, причем любой из них подгоняет один signed long long int.

A1=A>>32;
A0=A & 0xffffffff;
B1=B>>32;
B0=B & 0xffffffff;

AB_0=A0*B0;
AB_1=A0*B1+A1*B0;
AB_2=A1*B1;

То же самое для C*D.

Положив прямой путь, вычитание может быть выполнено для каждой пары AB_i и CD_i аналогично, используя дополнительный бит переноса (точно 1-битное целое) для каждого. Итак, если мы скажем E = A * B-C * D, вы получите что-то вроде:

E_00=AB_0-CD_0 
E_01=(AB_0 > CD_0) == (AB_0 - CD_0 < 0) ? 0 : 1  // carry bit if overflow
E_10=AB_1-CD_1 
...

Продолжаем переносом верхней половины E_10 на E_20 (сдвиг на 32 и добавим, затем стираем верхнюю половину E_10).

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

E_10 теперь имеет достаточно "пространства", чтобы взять верхнюю половину из E_00 (shift, add, erase) и бит переноса E_01.

E_10 теперь может быть больше, поэтому повторим передачу на E_20.

В этот момент E_20 должен стать нулем, иначе результат не будет соответствовать. Верхняя половина E_10 пуста и в результате переноса.

Последний шаг - снова передать нижнюю половину E_20 в E_10.

Если бы ожидалось, что E=A*B+C*D будет соответствовать тэгам signed long long int, теперь мы имеем

E_20=0
E_10=0
E_00=E

Ответ 10

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

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

Для демонстрации кода используются следующие signed char и unsigned char. Для вашей реализации измените определение Signed на typedef signed long long int Signed; и определение Unsigned на typedef unsigned long long int Unsigned;.

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>


//  Define the signed and unsigned types we wish to use.
typedef signed char   Signed;
typedef unsigned char Unsigned;

//  uHalfModulus is half the modulus of the unsigned type.
static const Unsigned uHalfModulus = UCHAR_MAX/2+1;

//  sHalfModulus is the negation of half the modulus of the unsigned type.
static const Signed   sHalfModulus = -1 - (Signed) (UCHAR_MAX/2);


/*  Map the unsigned value to the signed value that is the same modulo the
    modulus of the unsigned type.  If the input x maps to a positive value, we
    simply return x.  If it maps to a negative value, we return x minus the
    modulus of the unsigned type.

    In most C implementations, this routine could simply be "return x;".
    However, this version uses several steps to convert x to a negative value
    so that overflow is avoided.
*/
static Signed ConvertToSigned(Unsigned x)
{
    /*  If x is representable in the signed type, return it.  (In some
        implementations, 
    */
    if (x < uHalfModulus)
        return x;

    /*  Otherwise, return x minus the modulus of the unsigned type, taking
        care not to overflow the signed type.
    */
    return (Signed) (x - uHalfModulus) - sHalfModulus;
}


/*  Calculate A*B - C*D given that the result is representable as a Signed
    value.
*/
static signed char Calculate(Signed A, Signed B, Signed C, Signed D)
{
    /*  Map signed values to unsigned values.  Positive values are unaltered.
        Negative values have the modulus of the unsigned type added.  Because
        we do modulo arithmetic below, adding the modulus does not change the
        final result.
    */
    Unsigned a = A;
    Unsigned b = B;
    Unsigned c = C;
    Unsigned d = D;

    //  Calculate with modulo arithmetic.
    Unsigned t = a*b - c*d;

    //  Map the unsigned value to the corresponding signed value.
    return ConvertToSigned(t);
}


int main()
{
    //  Test every combination of inputs for signed char.
    for (int A = SCHAR_MIN; A <= SCHAR_MAX; ++A)
    for (int B = SCHAR_MIN; B <= SCHAR_MAX; ++B)
    for (int C = SCHAR_MIN; C <= SCHAR_MAX; ++C)
    for (int D = SCHAR_MIN; D <= SCHAR_MAX; ++D)
    {
        //  Use int to calculate the expected result.
        int t0 = A*B - C*D;

        //  If the result is not representable in signed char, skip this case.
        if (t0 < SCHAR_MIN || SCHAR_MAX < t0)
            continue;

        //  Calculate the result with the sample code.
        int t1 = Calculate(A, B, C, D);

        //  Test the result for errors.
        if (t0 != t1)
        {
            printf("%d*%d - %d*%d = %d, but %d was returned.\n",
                A, B, C, D, t0, t1);
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}

Ответ 11

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

AB - CD
= [ A(B - N) - C( D - M )] + [AN - CM]

= ( AK - CJ ) + ( AN - CM)

    where K = B - N
          J = D - M

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

Ответ 12

Для полноты, поскольку никто не упомянул об этом, некоторые компиляторы (например, GCC) фактически предоставляют вам 128-битное целое число в настоящее время.

Таким образом, простым решением может быть:

(long long)((__int128)A * B - (__int128)C * D)

Ответ 13

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Ни B/C, ни D/A не могут переполняться, поэтому сначала рассчитывайте (B/C-D/A). Поскольку конечный результат не будет переполняться в соответствии с вашим определением, вы можете безопасно выполнить оставшиеся умножения и вычислить (B/C-D/A)*A*C, который является требуемым результатом.

Обратите внимание, что если ваш вход может быть чрезвычайно мал, то B/C или D/A могут переполняться. Если возможно, возможно, потребуются более сложные манипуляции в соответствии с инспекцией ввода.

Ответ 14

Выберите K = a big number (например, K = A - sqrt(A))

A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D); // Avoid overflow.

Почему?

(A-K)*(B-K) = A*B - K*(A+B) + K^2
(C-K)*(D-K) = C*D - K*(C+D) + K^2

=>
(A-K)*(B-K) - (C-K)*(D-K) = A*B - K*(A+B) + K^2 - {C*D - K*(C+D) + K^2}
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B) + K*(C+D) + K^2 - K^2
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D)

Заметим, что Поскольку A, B, C и D являются большими числами, таким образом A-C и B-D являются маленькими числами.

Ответ 15

Возможно, я не охватил все случаи краев, и я не проверил это строго, но это реализует технику, которую я помню, используя в 80-х годах, когда пытаюсь выполнить 32-битную математику с целочисленным числом по 16-разрядному процессору. По сути, вы разделяете 32 бита на два 16-битных блока и работаете с ними отдельно.

public class DoubleMaths {
  private static class SplitLong {
    // High half (or integral part).
    private final long h;
    // Low half.
    private final long l;
    // Split.
    private static final int SPLIT = (Long.SIZE / 2);

    // Make from an existing pair.
    private SplitLong(long h, long l) {
      // Let l overflow into h.
      this.h = h + (l >> SPLIT);
      this.l = l % (1l << SPLIT);
    }

    public SplitLong(long v) {
      h = v >> SPLIT;
      l = v % (1l << SPLIT);
    }

    public long longValue() {
      return (h << SPLIT) + l;
    }

    public SplitLong add ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() + b.longValue() );
    }

    public SplitLong sub ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() - b.longValue() );
    }

    public SplitLong mul ( SplitLong b ) {
      /*
       * e.g. 10 * 15 = 150
       * 
       * Divide 10 and 15 by 5
       * 
       * 2 * 3 = 5
       * 
       * Must therefore multiply up by 5 * 5 = 25
       * 
       * 5 * 25 = 150
       */
      long lbl = l * b.l;
      long hbh = h * b.h;
      long lbh = l * b.h;
      long hbl = h * b.l;
      return new SplitLong ( lbh + hbl, lbl + hbh );
    }

    @Override
    public String toString () {
      return Long.toHexString(h)+"|"+Long.toHexString(l);
    }
  }

  // I'll use long and int but this can apply just as easily to long-long and long.
  // The aim is to calculate A*B - C*D without overflow.
  static final long A = Long.MAX_VALUE;
  static final long B = Long.MAX_VALUE - 1;
  static final long C = Long.MAX_VALUE;
  static final long D = Long.MAX_VALUE - 2;

  public static void main(String[] args) throws InterruptedException {
    // First do it with BigIntegers to get what the result should be.
    BigInteger a = BigInteger.valueOf(A);
    BigInteger b = BigInteger.valueOf(B);
    BigInteger c = BigInteger.valueOf(C);
    BigInteger d = BigInteger.valueOf(D);
    BigInteger answer = a.multiply(b).subtract(c.multiply(d));
    System.out.println("A*B - C*D = "+answer+" = "+answer.toString(16));

    // Make one and test its integrity.
    SplitLong sla = new SplitLong(A);
    System.out.println("A="+Long.toHexString(A)+" ("+sla.toString()+") = "+Long.toHexString(sla.longValue()));

    // Start small.
    SplitLong sl10 = new SplitLong(10);
    SplitLong sl15 = new SplitLong(15);
    SplitLong sl150 = sl10.mul(sl15);
    System.out.println("10="+sl10.longValue()+"("+sl10.toString()+") * 15="+sl15.longValue()+"("+sl15.toString()+") = "+sl150.longValue() + " ("+sl150.toString()+")");

    // The real thing.
    SplitLong slb = new SplitLong(B);
    SplitLong slc = new SplitLong(C);
    SplitLong sld = new SplitLong(D);
    System.out.println("B="+Long.toHexString(B)+" ("+slb.toString()+") = "+Long.toHexString(slb.longValue()));
    System.out.println("C="+Long.toHexString(C)+" ("+slc.toString()+") = "+Long.toHexString(slc.longValue()));
    System.out.println("D="+Long.toHexString(D)+" ("+sld.toString()+") = "+Long.toHexString(sld.longValue()));
    SplitLong sanswer = sla.mul(slb).sub(slc.mul(sld));
    System.out.println("A*B - C*D = "+sanswer+" = "+sanswer.longValue());

  }

}

Печать

A*B - C*D = 9223372036854775807 = 7fffffffffffffff
A=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
10=10(0|a) * 15=15(0|f) = 150 (0|96)
B=7ffffffffffffffe (7fffffff|fffffffe) = 7ffffffffffffffe
C=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
D=7ffffffffffffffd (7fffffff|fffffffd) = 7ffffffffffffffd
A*B - C*D = 7fffffff|ffffffff = 9223372036854775807

который выглядит так, как будто он работает.

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