Come evitare overflow in expr. A * B - C * D


161

Ho bisogno di calcolare un'espressione che assomigli a:, A*B - C*Ddove sono i loro tipi: signed long long int A, B, C, D; ogni numero può essere davvero grande (non traboccare il suo tipo). Mentre A*Bpotrebbe causare overflow, allo stesso tempo l'espressione A*B - C*Dpuò essere davvero piccola. Come posso calcolarlo correttamente?

Ad esempio:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1dove MAX = LLONG_MAX - ne n - un numero naturale.


17
Quanto è importante l'accuratezza?
Anirudh Ramanathan,

1
@Cthulhu, ottima domanda. Poteva provare a fare una funzione equivalente usando un numero più piccolo dividendoli tutti per 10 o qualcosa del genere, quindi moltiplicando il risultato.
Chris,

4
Vars A, B, C, D sono firmate. Ciò implica che A - Cpotrebbe traboccare. È un problema da considerare o sai che questo non accadrà con i tuoi dati?
William Morris,

2
@MooingDuck ma è possibile controllare in anticipo se l'operazione traboccherà stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@ Chris: No, sto dicendo che non esiste un modo portatile per verificare se si è verificato un overflow firmato. (Brad è corretto che si può rilevare portabile che sarà accadere). L'uso dell'assemblaggio in linea è uno dei molti modi non portatili per verificare.
Mooing Duck,

Risposte:


120

Sembra troppo banale, immagino. Ma A*Bè quello che potrebbe traboccare.

Potresti fare quanto segue, senza perdere precisione

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)

Questa decomposizione può essere fatta ulteriormente .
Come ha sottolineato @Gian, potrebbe essere necessario prestare attenzione durante l'operazione di sottrazione se il tipo non è firmato a lungo.


Ad esempio, con il caso che hai nella domanda, ci vuole solo una iterazione,

 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, applica lo stesso algoritmo aC*D
Chris

2
Penso che dovresti spiegare cosa rappresenta E.
Caleb,

7
Sia long long che double sono 64 bit. Poiché il doppio deve allocare alcuni bit per l'esponente, ha un intervallo più piccolo di valori possibili senza perdita di precisione.
Jim Garrison,

3
@Cthulhu - mi sembra che questo funzionerebbe solo se tutti i numeri fossero molto grandi ... per esempio, avresti comunque un overflow con {A, B, C, D} = {MAX, MAX, MAX, 2}. L'OP dice "Ogni numero può essere davvero grande", ma dall'affermazione del problema non è chiaro che ogni numero deve essere davvero grande.
Kevin K,

4
E se qualcuno di fosse A,B,C,Dnegativo, però? Non sarà Eo Fsarà ancora più grande allora?
Supr,

68

La soluzione più semplice e più generale è quella di utilizzare una rappresentazione che non può traboccare, utilizzando una libreria di numeri interi lunghi (ad esempio http://gmplib.org/ ) o rappresentando utilizzando una struttura o un array e implementando una sorta di lunga moltiplicazione ( cioè separando ciascun numero a due metà a 32 bit ed eseguendo la moltiplicazione come di seguito:

(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

Supponendo che il risultato finale si adatti a 64 bit, in realtà non è realmente necessario la maggior parte dei bit di R3 e nessuno di R4


8
Il calcolo di cui sopra non è davvero così complicato come sembra, è davvero una semplice moltiplicazione lunga in base 2 ^ 32 e il codice in C dovrebbe apparire più semplice. Inoltre, sarà una buona idea creare funzioni generiche per fare questo lavoro nel tuo programma.
Ofir,

46

Si noti che questo non è standard poiché si basa su un overflow firmato e avvolgente. (GCC ha flag di compilatore che lo abilitano.)

Ma se esegui tutti i calcoli long long, il risultato dell'applicazione diretta della formula:
(A * B - C * D)sarà accurato fintanto che il risultato corretto si inserirà in a long long.


Ecco una soluzione alternativa che si basa solo sul comportamento definito dall'implementazione del cast di numeri interi senza segno in numeri interi con segno. Ma ci si può aspettare che questo funzioni su quasi tutti i sistemi oggi.

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

In questo modo vengono immessi gli input in unsigned long longcui il comportamento di overflow è garantito dallo standard. Il casting a un intero con segno alla fine è la parte definita dall'implementazione, ma funzionerà su quasi tutti gli ambienti oggi.


Se hai bisogno di una soluzione più pedante, penso che devi usare "aritmetica lunga"


+1 Sei l'unico a notare questo. L'unica parte difficile è impostare il compilatore in modo da eseguire un overflow avvolgente e verificare se il risultato corretto si adatta effettivamente a long long.
Mistico il

2
Anche la versione ingenua senza alcun trucco farà la cosa giusta sulla maggior parte delle implementazioni; non è garantito dallo standard, ma dovresti trovare una macchina con complemento a 1 o qualche altro dispositivo abbastanza strano per farlo fallire.
Hobbs

1
Penso che questa sia una risposta importante. Sono d'accordo che potrebbe non essere una programmazione corretta assumere un comportamento specifico dell'implementazione, ma ogni ingegnere dovrebbe comprendere l'aritmetica del modulo e come ottenere i flag del compilatore giusti per garantire un comportamento coerente se le prestazioni sono essenziali. Gli ingegneri DSP fanno affidamento su questo comportamento per implementazioni di filtri a punto fisso, per le quali la risposta accettata avrà prestazioni inaccettabili.
Peter M,

18

Questo dovrebbe funzionare (penso):

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);

Ecco la mia derivazione:

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
Grazie @bradgonesurfing - potresti fornire un tale input? Ho aggiornato la mia risposta, eseguita e funziona (bd e ca sono 0) ...
paquetp

1
Hmmm. Ora ci penso forse no. Caso degenerato con d = 1 e a = 1 e b = maxint ec = maxint funziona ancora. Cool :)
bradgonesurfing

1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (la nota c è negativa). Intelligente però, sono certo che il tuo codice gestisce correttamente tutti i numeri positivi.
Mooing Duck il

3
@MooingDuck ma anche la risposta finale per il tuo set è traboccata, quindi non è una configurazione valida. Funziona solo se ogni lato ha lo stesso segno, quindi la sottrazione risultante è nel raggio d'azione.
bradgonesurfing,

1
C'è qualcosa di strano con StackOverflow quando questa risposta, che è la più semplice e la migliore, ha un punteggio così basso rispetto alla risposta più alta.
bradgonesurfing,

9

Potresti considerare il calcolo del più grande fattore comune per tutti i tuoi valori, e poi dividerli per quel fattore prima di fare le tue operazioni aritmetiche, quindi moltiplicare di nuovo. Ciò presuppone l'esistenza di tale fattore di questo, (ad esempio, se A, B, Ce Dcapita di essere primi tra loro, non avranno un fattore comune).

Allo stesso modo, potresti considerare di lavorare su scale di log, ma questo sarà un po 'spaventoso, soggetto a precisione numerica.


1
Logarithming sembra buono se long doubleè disponibile. In tal caso, è possibile raggiungere un livello accettabile di precisione (e il risultato viene arrotondato).

9

Se il risultato rientra in un int lungo lungo, allora l'espressione A * BC * D va bene poiché esegue l'aritmetica mod 2 ^ 64 e darà il risultato corretto. Il problema è sapere se il risultato rientra in un int molto lungo. Per rilevare ciò, puoi usare il seguente trucco usando i doppi:

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

Il problema con questo approccio è che sei limitato dalla precisione della mantissa dei doppi (54 bit?), Quindi devi limitare i prodotti A * B e C * D a 63 + 54 bit (o probabilmente un po 'meno).


Questo è l'esempio più pratico. Cancella e dà la risposta giusta (o genera un'eccezione quando gli input sono cattivi).
Mark Lakata,

1
Bello ed elegante! Non ti sei innamorato della trappola per cui gli altri si sono innamorati. Ancora un'altra cosa: scommetto che ci sono alcuni esempi in cui il doppio calcolo è inferiore a MAX_LLONG solo a causa di errori di arrotondamento. Il mio istinto matematico mi dice che dovresti invece calcolare la differenza tra il doppio e il risultato lungo e confrontarlo con MAX_LLONG / 2 o qualcosa del genere. Questa differenza sono gli errori di arrotondamento del doppio calcolo e più l'overflow e dovrebbero normalmente essere relativamente bassi, ma nel caso in cui ho accennato sarà grande. Ma in questo momento sono troppo pigro per scoprirlo di sicuro. :-)
Hans-Peter Störr,

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

poi

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

7

Puoi scrivere ogni numero in un array, ogni elemento è una cifra e fare i calcoli come polinomi . Prendi il polinomio risultante, che è un array, e calcola il risultato moltiplicando ogni elemento dell'array per 10 per la potenza della posizione nell'array (la prima posizione è la più grande e l'ultima è zero).

Il numero 123può essere espresso come:

123 = 100 * 1 + 10 * 2 + 3

per il quale è sufficiente creare un array [1 2 3] .

Lo fai per tutti i numeri A, B, C e D, quindi li moltiplichi come polinomi. Una volta ottenuto il polinomio risultante, devi solo ricostruire il numero da esso.


2
non so cosa sia ma dovrò trovarlo. mettere :) . questa è una soluzione della parte superiore della mia testa mentre faccio shopping con la mia ragazza :)
Mihai,

stai implementando i bignum in un array base10. GMP è una libreria di qualità bignum che utilizza la base 4294967296. MOLTO più veloce. Nessun voto negativo, perché la risposta è corretta e utile.
Mooing Duck il

Grazie :) . è utile sapere che questo è un modo per farlo, ma ci sono modi migliori, quindi non farlo in questo modo. almeno non in questa situazione :)
Mihai,

comunque ... usando questa soluzione potresti computerizzare un numero molto più grande di qualsiasi tipo primitivo in grassetto (come numeri di 100 cifre) e mantenere il risultato come un array. questo merita un voto positivo: p
Mihai,

Non sono sicuro che ottenga un voto, dal momento che questo metodo (sebbene efficace e relativamente facile da capire) è affamato di memoria e lento.
Mooing Duck il

6

Mentre un signed long long intnon regge A*B, due lo faranno. Quindi A*Bpotrebbe essere scomposto in termini dell'albero di esponente diverso, ognuno dei quali si adatta a uno 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;

Lo stesso per C*D.

Seguendo la strada diritta, la sottrazione potrebbe essere fatta per ogni coppia di AB_ie CD_iallo stesso modo, usando un bit di carry aggiuntivo (esattamente un numero intero di 1 bit) per ciascuno. Quindi se diciamo E = A * BC * D ottieni qualcosa del tipo:

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 
...

Continuiamo trasferendo la metà superiore di E_10a E_20(sposta di 32 e aggiungi, quindi cancella la metà superiore di E_10).

Ora puoi eliminare il bit di riporto E_11aggiungendolo con il segno giusto (ottenuto dalla parte non riporto) a E_20. Se questo innesca un overflow, il risultato non si adatterebbe neanche.

E_10ora ha abbastanza 'spazio' per prelevare la metà superiore da E_00 (sposta, aggiungi, cancella) e il bit di trasporto E_01.

E_10potrebbe essere di nuovo più grande, quindi ripetiamo il trasferimento a E_20.

A questo punto, E_20deve diventare zero, altrimenti il ​​risultato non si adatta. La metà superiore diE_10 è vuota a seguito del trasferimento.

Il passo finale è quello di trasferire la metà inferiore E_20in E_10nuovamente.

Se l'aspettativa che E=A*B+C*Dsi adatterebbe alle signed long long intprese, ora abbiamo

E_20=0
E_10=0
E_00=E

1
Questa è in realtà la formula semplificata che si otterrebbe se si utilizza la formula di moltiplicazione di Ofir e si rimuovono tutti i risultati temporanei inutili.
dronus,

3

Se sai che il risultato finale è rappresentabile nel tuo tipo intero, puoi eseguire questo calcolo rapidamente usando il codice qui sotto. Poiché lo standard C specifica che l'aritmetica senza segno è l'aritmetica del modulo e non trabocca, è possibile utilizzare un tipo senza segno per eseguire il calcolo.

Il codice seguente presuppone l'esistenza di un tipo senza segno della stessa larghezza e che il tipo con segno utilizzi tutti i modelli di bit per rappresentare i valori (nessuna rappresentazione trap, il minimo del tipo con segno è negativo della metà del modulo del tipo senza segno). Se ciò non è valido in un'implementazione C, è possibile apportare semplici modifiche alla routine ConvertToSigned.

Quanto segue utilizza signed chare unsigned charper dimostrare il codice. Per l'implementazione, cambiare la definizione di Signedad typedef signed long long int Signed;e la definizione di Unsigneda 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

Potresti provare a spezzare l'equazione in componenti più piccoli che non traboccano.

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

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

    where K = B - N
          J = D - M

Se i componenti continuano a traboccare, è possibile suddividerli in componenti più piccoli in modo ricorsivo e ricombinarli.


Questo può essere o non essere corretto, ma è sicuramente confuso. Definisci Ke J, perché no Ne M. Inoltre, penso che stai rompendo l'equazione in pezzi più grandi . Poiché il tuo passaggio 3 è lo stesso della domanda del PO, tranne che per i più complicati (AK-CJ)->(AB-CD)
Mooing Duck

N non è semplificato da nulla. È solo un numero sottratto da A per renderlo più piccolo. In realtà è una soluzione simile ma inferiore a paquetp. Qui sto usando la sottrazione invece della divisione intera per renderla più piccola.
bradgonesurfing,

2

Potrei non aver coperto tutti i casi limite, né ho rigorosamente testato questo, ma questo implementa una tecnica che ricordo di aver usato negli anni '80 quando ho provato a fare matematica a 32 bit interi su una CPU a 16 bit. In sostanza, dividi i 32 bit in due unità a 16 bit e lavori separatamente con essi.

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());

  }

}

stampe:

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

che mi sembra che funzioni.

Scommetto che ho perso alcune delle sottigliezze come guardare per il trabocco dei segni ecc., Ma penso che l'essenza sia lì.


1
Penso che questa sia un'implementazione di ciò che ha suggerito @Ofir.
OldCurmudgeon

2

Per completezza, poiché nessuno lo ha menzionato, alcuni compilatori (ad esempio GCC) attualmente forniscono un intero a 128 bit.

Quindi una soluzione semplice potrebbe essere:

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

1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Né B/CD/Apuò traboccare, in modo da calcolare (B/C-D/A)prima. Poiché il risultato finale non trabocca in base alla tua definizione, puoi eseguire in sicurezza le moltiplicazioni rimanenti e calcolare (B/C-D/A)*A*Cqual è il risultato richiesto.

Nota, se anche il tuo input può essere estremamente piccolo , B/Co D/Apuò traboccare. Se possibile, potrebbero essere necessarie manipolazioni più complesse in base all'ispezione degli input.


2
Ciò non funzionerà poiché la divisione intera perde informazioni (la frazione del risultato)
Ofir,

@Fir è corretto, tuttavia non puoi mangiare la torta e lasciarla intatta. Devi pagare con precisione o utilizzando risorse aggiuntive (come hai suggerito nella tua risposta). La mia risposta è di natura matematica mentre la tua è orientata al computer. Ognuno può essere corretto a seconda delle circostanze.
SomeWittyUsername

2
Hai ragione - avrei dovuto dirlo come - non darà un risultato esatto piuttosto che non funzionerà, poiché la matematica è corretta. Tuttavia, notare nei casi che probabilmente interessano il richiedente (ad esempio nell'esempio nella domanda), l'errore sarà probabilmente sorprendentemente grande, molto più grande di quanto possa essere accettabile per qualsiasi applicazione pratica. In ogni caso, è stata una risposta perspicace e non avrei dovuto usare quella lingua.
Ofir,

@Fir credo che la tua lingua non sia appropriata. Il PO ha chiaramente richiesto un calcolo "corretto", non uno che perderebbe la precisione per essere eseguito con vincoli di risorse estremi.
user4815162342

1

Scegliere K = a big number (es. K = A - sqrt(A))

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

Perché?

(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)

Si noti che poiché A, B, C e D sono numeri grandi, quindi A-Ce B-Dsono numeri piccoli.


Come si sceglie K in pratica? Inoltre, K * (A-C + BD) potrebbe ancora traboccare.
ylc,

@ylc: scegli K = sqrt (A), non A-C+B-Dè un numero piccolo. Perché A, B, C e D sono numeri grandi, quindi AC è un numero piccolo.
Amir Saniyan,

Se si sceglie K = sqrt (A) , allora (AK) * (BK) potrebbe traboccare di nuovo.
YLC

@ylc: OK! Lo cambio in A - sqrt(A):)
Amir Saniyan il

Quindi K * (A-C + BD) potrebbe traboccare.
YLC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.