Як уникнути переповнення в expr. А Б В Г


161

Мені потрібно обчислити вираз, який виглядає так:, 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 - деяке натуральне число.


17
Наскільки важлива точність?
Аніруд Рамананат

1
@Cthulhu, чудове запитання. Він міг би спробувати зробити еквівалентну функцію, використовуючи меншу кількість, поділивши їх на 10 чи щось, а потім помноживши результат.
Кріс

4
Vars A, B, C, D підписані. Це означає, що A - Cможе переповнитися. Це питання, який слід розглянути чи ви знаєте, що це не відбудеться з вашими даними?
Вільям Морріс

2
@MooingDuck але ви можете перевірити заздалегідь , якщо операція буде переповнення stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@Chris: Ні, я кажу, що немає переносного способу перевірити, чи не відбулося підписане переповнення. (Brad правильно , що ви можете переносимо виявити , що це буде відбуватися). Використання вбудованої збірки є одним із багатьох способів перевірки.
Mooing Duck

Відповіді:


120

Здається, це здається занадто банальним. Але 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, під час операції віднімання, можливо, потрібно буде бути обережним, якщо тип довго не підписується.


Наприклад, у випадку, який ви маєте в запитанні, потрібно лише одну ітерацію,

 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

4
@Caleb, просто застосуй той самий алгоритм доC*D
Chris Chris

2
Я думаю, ви повинні пояснити, що собою являє Е.
Калеб

7
І довгий, і подвійний - 64 біти. Оскільки подвійний повинен виділяти деякі біти для експонента, він має менший діапазон можливих значень без втрати точності.
Джим Гаррісон

3
@Cthulhu - мені здається, що це спрацювало б лише в тому випадку, якщо все число дуже велике ... наприклад, ви все одно переповнювали б {A, B, C, D} = {MAX, MAX, MAX, 2}. ОП говорить: "Кожне число може бути дійсно великим", але з твердження проблеми не видно, що кожне число має бути дійсно великим.
Кевін К

4
Що робити, якщо хтось із A,B,C,Dнегативних? Не буде Eчи не Fбуде ще більшим тоді?
Супр

68

Найпростішим і найзагальнішим рішенням є використання представлення, яке не може переповнювати, або за допомогою довгої цілочисельної бібліотеки (наприклад, 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


8
Розрахунок вище насправді не такий складний, як здається, це дійсно просте довге множення в базі 2 ^ 32, і код на C повинен виглядати простіше. Крім того, буде хорошою ідеєю створити загальні функції для виконання цієї роботи у вашій програмі.
Офір

46

Зауважте, що це не є стандартним, оскільки воно покладається на обтікання підписом-підписом. (GCC має прапори компілятора, які це дозволяють.)

Але якщо ви просто зробите всі обчислення long long, результат прямого застосування формули:
(A * B - C * D)буде точним, доки правильний результат впишеться в a long long.


Ось обхід, який покладається лише на визначену реалізацією поведінку викидання непідписаного цілого числа до підписаного цілого числа. Але це, як можна очікувати, буде працювати майже в кожній системі сьогодні.

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

Це відкидає вхідні дані, unsigned long longде стандартна нормально гарантовано перекриває поведінку переливу. Повернення до підписаного цілого числа в кінці - це частина, визначена реалізацією, але сьогодні буде працювати майже в усіх середовищах.


Якщо вам потрібно більш педантичне рішення, я думаю, вам доведеться використовувати "довгу арифметику"


+1 Ви єдиний, хто це помітив. Єдиною складною частиною є встановлення компілятора, щоб виконувати переповнення та перевірити, чи правильний результат насправді підходить до long long.
Містичне

2
Навіть наївна версія без жодних хитрощів зробить правильно в більшості реалізацій; це не гарантується стандартом, але вам доведеться знайти машину з доповненням 1 або якийсь інший досить дивний пристрій, щоб зробити його не вдається.
варення

1
Я думаю, що це важлива відповідь. Я погоджуюсь, що це може бути не правильне програмування, щоб припускати поведінку, що залежить від впровадження, але кожен інженер повинен розуміти модульну арифметику і як отримати правильні прапорці компілятора, щоб забезпечити послідовну поведінку, якщо продуктивність є важливою. Інженери DSP покладаються на цю поведінку для реалізації фільтрів з фіксованою точкою, для яких прийнята відповідь матиме неприйнятну ефективність.
Пітер М

18

Це має працювати (я думаю):

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)

1
Спасибі @bradgonesurfing - ви могли б надати такий вклад? Я оновив свою відповідь, виконав її, і вона працює (bd і ca є 0) ...
paquetp

1
Хммм. Зараз я думаю про це, можливо, ні. Вироджений випадок з d = 1 і a = 1 і b = maxint і c = maxint, він все ще працює. Cool :)
bradgonesurfing

1
@paquetp: a = 1, b = 0x7fffffffffffffffff, c = -0x7fffffffffffffffff, d = 1 (примітка c від'ємна). Хоча розумно, я впевнений, що ваш код правильно обробляє всі додатні цифри.
Mooing Duck

3
@MooingDuck, але остаточна відповідь для вашого набору також переповнена, тому це не є дійсною установкою. Він працює лише в тому випадку, якщо кожна сторона має один і той же знак, тож віднімання, що виходить, знаходиться в межах.
bradgonesurfing

1
З StackOverflow є щось дивне, коли ця відповідь найпростіша і найкраща отримала такий низький бал у порівнянні з найкращою оцінкою.
bradgonesurfing

9

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

Так само ви можете розглянути можливість роботи з масштабними журналами, але це буде трохи страшно, за умови чисельної точності.


1
Логарифмінг здається гарним, якщо long doubleє. У цьому випадку можна досягти прийнятного рівня точності (і цензуру результату округлити).

9

Якщо результат укладається в довгий довгий int, то вираз A * BC * D добре, оскільки він виконує арифметичний mod 2 ^ 64, і дасть правильний результат. Проблема полягає в тому, щоб знати, чи підходить результат у довгий довгий 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 біт (або, можливо, трохи менше).


Це найбільш практичний приклад. Очистити та дати правильну відповідь (або кидає виняток, коли входи погані).
Марк Лаката

1
Приємно і елегантно! Ви не потрапили на пастку, за яку потрапили інші. Ще одне: я б заперечив, що є кілька прикладів, коли подвійний обчислення знаходиться нижче MAX_LLONG лише через помилки округлення. Мій математичний інстинкт підказує, що вам слід замість цього обчислити різницю подвійного та довгого результату та порівняти його з MAX_LLONG / 2 чи іншим. Ця різниця є помилками округлення подвійного обчислення і плюс перелив, і зазвичай повинна бути відносно низькою, але у випадку, про який я згадував, воно буде великим. Але саме зараз я лінивий, щоб довідатися точно. :-)
Ганс-Пітер Стрерр

9
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

7

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

Цифра 123може бути виражена як:

123 = 100 * 1 + 10 * 2 + 3

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

Ви робите це для всіх чисел A, B, C і D, а потім перемножуєте їх на многочлени. Коли ви отримаєте поліном, ви просто реконструюєте число з нього.


2
не знаю, що це, але мені доведеться знайти. поставити :). це рішення верхньої частини моєї голови, коли я роблю покупки зі своєю дівчиною :)
Mihai

Ви реалізуєте bignums в масиві base10. GMP - це якісна бібліотека bignum, яка використовує базу 4294967296. МНОГО швидше. Немає жодної протидії, оскільки відповідь правильна і корисна.
Mooing Duck

Дякую :) . корисно знати, що це один із способів, але є кращі способи, тому не робіть цього. принаймні, не в цій ситуації :)
Mihai

у будь-якому випадку ... використовуючи це рішення, ви могли б отримати на комп'ютері набагато більшу кількість, ніж будь-який примітивний тип міг жирним шрифтом (наприклад, 100-значне число) і зберегти результат як масив. це заслуговує на голосування: p
Міхай

Я не впевнений, що він отримує високу оцінку, оскільки цей метод (хоча і ефективний і порівняно простий для розуміння) є голодним і повільним.
Mooing Duck

6

Поки a 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 * BC * 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 (зміщення, додавання, видалення) та біт для перенесення 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

1
Це насправді спрощена формула, яку можна було б отримати, якщо використовувати формулу множення Офіра та видалити всі непотрібні тимчасові результати.
дрону

3

Якщо ви знаєте, що кінцевий результат представлений у вашому цілому типі, ви можете швидко виконати цей обчислення, використовуючи наведений нижче код. Оскільки стандарт 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;
}

2

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

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

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

    where K = B - N
          J = D - M

Якщо компоненти все ще переповнюються, ви можете розбити їх на більш дрібні компоненти рекурсивно, а потім рекомбінувати.


Це може бути, а може й не правильно, але, безумовно, заплутано. Ви визначаєте, Kа Jчому б Nі ні M. Крім того, я думаю, ви розбиваєте рівняння на більші шматки. Оскільки ваш крок 3 такий же, як і питання щодо ОП, за винятком складнішого (AK-CJ)->(AB-CD)
Mooing Duck

N ніщо не спрощується. Це просто число, віднімане від A, щоб зробити його меншим. Насправді це схоже, але неповноцінне рішення для пакета. Тут я використовую віднімання замість цілого поділу, щоб зменшити його.
bradgonesurfing

2

Я, можливо, не охоплював усіх крайових випадків, а також не проводив жорсткого тестування цього, але це реалізує методику, яку я пам’ятаю, використовуючи у 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

що мені здається, що це працює.

Б'юсь об заклад, я пропустив деякі тонкощі, такі як спостереження за переповненням знаків тощо.


1
Я думаю, це реалізація того, що запропонував @Ofir.
OldCurmudgeon

2

Для повноти, оскільки ніхто про це не згадував, деякі компілятори (наприклад, GCC) насправді надають вам 128-бітове ціле число.

Таким чином, легким рішенням може бути:

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

1

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може переповниться. Якщо це можливо, можуть знадобитися більш складні маніпуляції відповідно до вхідної перевірки.


2
Це не спрацює, оскільки ціле ділення втрачає інформацію (частка результату)
Ofir

@Ofir це правильно, проте ви не можете з'їсти торт і залишити його незайманим. Ви повинні платити або точно, або використовуючи додаткові ресурси (як ви запропонували у своїй відповіді). Моя відповідь має математичний характер, тоді як ваша 'орієнтована на комп’ютер. Кожен може бути правильним залежно від обставин.
SomeWittyUsername

2
Ви маєте рацію - я мав би це сформулювати так - не дасть точного результату, а не спрацює, оскільки математика правильна. Однак зауважте у випадках, які, ймовірно, цікавлять представника питання (наприклад, у прикладі у запитанні), помилка, ймовірно, буде напрочуд великою - значно більшою, ніж може бути прийнятною для будь-якого практичного застосування. У будь-якому випадку - це була прониклива відповідь, і я не повинен був використовувати цю мову.
Офір

@Ofir Я не думаю, що ваша мова була невідповідною. ОП чітко вимагала "правильного" розрахунку, а не того, який би втрачав точність заради виконання в умовах крайніх обмежень ресурсів.
user4815162342

1

Виберіть 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це малі числа.


Як ви обираєте K у практичному? Крім того, K * (A-C + BD) все ще може переповнюватися.
ilc

@ylc: оберіть K = sqrt (A). Це A-C+B-Dне мала кількість. Оскільки A, B, C і D - це великі числа, отже, AC - це невелика кількість.
Амір Саніян

Якщо ви виберете K = sqrt (A) , то (AK) * (BK) може знову переповнитися.
ilc

@ylc: Гаразд! Я A - sqrt(A)
змінюю

Тоді K * (A-C + BD) може переповнитися.
ilc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.