So vermeiden Sie einen Überlauf in expr. A B C D

161

Ich muss einen Ausdruck berechnen, der aussieht wie : A*B - C*D, wo ihre Typen sind: signed long long int A, B, C, D; Jede Zahl kann wirklich groß sein (ohne ihren Typ zu überlaufen). Während A*Bdies zu einem Überlauf führen kann, A*B - C*Dkann der Ausdruck gleichzeitig sehr klein sein. Wie kann ich es richtig berechnen?

Zum Beispiel : MAX * MAX - (MAX - 1) * (MAX + 1) == 1, wo MAX = LLONG_MAX - nund n - eine natürliche Zahl.

NGix
quelle
17
Wie wichtig ist Genauigkeit?
Anirudh Ramanathan
1
@Cthulhu, tolle Frage. Er könnte versuchen, eine äquivalente Funktion mit einer kleineren Zahl zu erstellen, indem er alle durch 10 oder so dividiert und dann das Ergebnis multipliziert.
Chris
4
Die Vars A, B, C, D sind signiert. Dies impliziert einen A - Cmöglichen Überlauf. Ist das ein zu berücksichtigendes Problem oder wissen Sie, dass dies mit Ihren Daten nicht passieren wird?
William Morris
2
@MooingDuck, aber Sie können im Voraus überprüfen, ob die Operation überläuft stackoverflow.com/a/3224630/158285
Bradgonesurfing
1
@ Chris: Nein, ich sage, dass es keine tragbare Möglichkeit gibt, zu überprüfen, ob ein signierter Überlauf aufgetreten ist. (Brad ist richtig , dass Sie kann portably erkennen , dass es wird passieren). Die Verwendung der Inline-Baugruppe ist eine von vielen nicht tragbaren Methoden zur Überprüfung.
Mooing Duck

Antworten:

120

Das scheint mir zu trivial. Aber A*Bist derjenige, der überlaufen könnte.

Sie können Folgendes tun, ohne an Präzision zu verlieren

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)

Diese Zersetzung kann weiter erfolgen .
Wie @Gian hervorhob, muss während der Subtraktionsoperation möglicherweise vorsichtig vorgegangen werden, wenn der Typ lange vorzeichenlos ist.


In dem Fall, den Sie in der Frage haben, ist beispielsweise nur eine Iteration erforderlich.

 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
Anirudh Ramanathan
quelle
4
@Caleb, wenden Sie einfach den gleichen Algorithmus aufC*D
Chris
2
Ich denke, Sie sollten erklären, was E darstellt.
Caleb
7
Sowohl long long als auch double sind 64 Bit. Da double dem Exponenten einige Bits zuweisen muss, hat es einen kleineren Bereich möglicher Werte ohne Genauigkeitsverlust.
Jim Garrison
3
@Cthulhu - scheint mir, dass dies nur funktionieren würde, wenn alle Zahlen sehr groß wären ... zum Beispiel hätten Sie immer noch einen Überlauf mit {A, B, C, D} = {MAX, MAX, MAX, 2}. Die OP sagt : „Jede Zahl kann wirklich groß sein“, aber es ist von der Problemstellung nicht klar , dass jede Zahl muss wirklich groß sein.
Kevin K
4
Was ist, wenn A,B,C,Deinige davon negativ sind? Wird Eoder wird Fes dann nicht noch größer?
Supr
68

Die einfachste und allgemeinste Lösung besteht darin, eine Darstellung zu verwenden, die nicht überlaufen kann, entweder mithilfe einer langen Ganzzahlbibliothek (z. B. http://gmplib.org/ ) oder mithilfe einer Struktur oder eines Arrays und der Implementierung einer Art langer Multiplikation ( dh jede Zahl in zwei 32-Bit-Hälften trennen und die Multiplikation wie folgt durchführen:

(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

Angenommen, das Endergebnis passt in 64 Bit, dann benötigen Sie die meisten Bits von R3 und keines von R4 nicht wirklich

Ofir
quelle
8
Die obige Berechnung ist wirklich nicht so kompliziert wie es aussieht, es ist wirklich eine einfache lange Multiplikation in Basis 2 ^ 32, und der Code in C sollte einfacher aussehen. Es ist auch eine gute Idee, generische Funktionen zu erstellen, um diese Arbeit in Ihrem Programm auszuführen.
Ofir
46

Beachten Sie, dass dies kein Standard ist, da es sich um einen umlaufenden signierten Überlauf handelt. (GCC verfügt über Compiler-Flags, die dies ermöglichen.)

Wenn Sie jedoch nur alle Berechnungen in ausführen long long, ist das Ergebnis der direkten Anwendung der Formel:
(A * B - C * D)genau, solange das richtige Ergebnis in a passt long long.


Hier ist eine Problemumgehung, die nur auf dem implementierungsdefinierten Verhalten beim Umwandeln einer vorzeichenlosen Ganzzahl in eine vorzeichenbehaftete Ganzzahl beruht. Es ist jedoch zu erwarten, dass dies heute auf fast jedem System funktioniert.

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

Dadurch werden die Eingaben dahin verschoben, unsigned long longwo das Überlaufverhalten vom Standard garantiert umgangen wird. Das Zurücksetzen auf eine vorzeichenbehaftete Ganzzahl am Ende ist der von der Implementierung definierte Teil, funktioniert jedoch heute in fast allen Umgebungen.


Wenn Sie eine pedantischere Lösung benötigen, müssen Sie meiner Meinung nach "lange Arithmetik" verwenden.

RiaD
quelle
+1 Du bist der einzige, der das bemerkt. Der einzige schwierige Teil besteht darin, den Compiler so einzustellen, dass er einen Wrap-Around-Überlauf ausführt, und zu überprüfen, ob das richtige Ergebnis tatsächlich in a passt long long.
Mysticial
2
Selbst die naive Version ohne Tricks wird bei den meisten Implementierungen das Richtige tun . Es ist nicht durch den Standard garantiert, aber Sie müssten eine 1-Komplement-Maschine oder ein anderes ziemlich seltsames Gerät finden, um es zum Scheitern zu bringen.
Hobbs
1
Ich denke, das ist eine wichtige Antwort. Ich bin damit einverstanden, dass es möglicherweise nicht korrekt programmiert ist, ein implementierungsspezifisches Verhalten anzunehmen, aber jeder Ingenieur sollte die Modulo-Arithmetik verstehen und wissen, wie man die richtigen Compiler-Flags erhält, um ein konsistentes Verhalten sicherzustellen, wenn die Leistung wesentlich ist. DSP-Ingenieure verlassen sich bei Festpunktfilterimplementierungen auf dieses Verhalten, bei denen die akzeptierte Antwort eine inakzeptable Leistung aufweist.
Peter M
18

Das sollte funktionieren (denke ich):

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

Hier ist meine Ableitung:

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)
paquetp
quelle
1
Danke @bradgonesurfing - könnten Sie einen solchen Input liefern? Ich habe meine Antwort aktualisiert, ausgeführt und es funktioniert (bd und ca sind 0) ...
paquetp
1
Hmmm. Jetzt denke ich vielleicht nicht darüber nach. Entarteter Fall mit d = 1 und a = 1 und b = maxint und c = maxint funktioniert es immer noch. Cool :)
Bradgonesurfing
1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (Anmerkung c ist negativ). Clever, ich bin sicher, Ihr Code behandelt alle positiven Zahlen korrekt.
Mooing Duck
3
@MooingDuck, aber die endgültige Antwort für Ihr Set ist ebenfalls übergelaufen, sodass es sich nicht um ein gültiges Setup handelt. Es funktioniert nur, wenn jede Seite das gleiche Vorzeichen hat, sodass die resultierende Subtraktion innerhalb des Bereichs liegt.
Bradgonesurfing
1
StackOverflow hat etwas Seltsames, wenn diese Antwort, die die einfachste und beste ist, im Vergleich zur am besten bewerteten Antwort eine so niedrige Punktzahl aufweist.
Bradgonesurfing
9

Sie könnten in Betracht ziehen, einen der größten gemeinsamen Faktoren für alle Ihre Werte zu berechnen und diese dann durch diesen Faktor zu dividieren, bevor Sie Ihre arithmetischen Operationen ausführen und dann erneut multiplizieren. Dies setzt voraus , dass ein solcher Faktor besteht jedoch (zum Beispiel, wenn A, B, Cund Dpassieren prim sein, werden sie keinen gemeinsamen Faktor haben).

In ähnlicher Weise könnten Sie in Betracht ziehen, an Log-Skalen zu arbeiten, aber dies wird ein wenig beängstigend sein, abhängig von der numerischen Genauigkeit.

Gian
quelle
1
Logarithmus scheint gut zu sein, wenn long doubleverfügbar. In diesem Fall kann ein akzeptables Maß an Präzision erreicht werden (und das Ergebnis kann gerundet werden).
9

Wenn das Ergebnis in ein langes langes int passt, ist der Ausdruck A * BC * D in Ordnung, da er den arithmetischen Mod 2 ^ 64 ausführt und das richtige Ergebnis liefert. Das Problem ist zu wissen, ob das Ergebnis in eine lange lange int passt. Um dies zu erkennen, können Sie den folgenden Trick mit Doppel verwenden:

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

Das Problem bei diesem Ansatz ist, dass Sie durch die Genauigkeit der Mantisse der Doppel (54 Bit?) Begrenzt sind, sodass Sie die Produkte A * B und C * D auf 63 + 54 Bit (oder wahrscheinlich etwas weniger) beschränken müssen.

Esteban Crespi
quelle
Dies ist das praktischste Beispiel. Löschen und gibt die richtige Antwort (oder löst eine Ausnahme aus, wenn die Eingaben schlecht sind).
Mark Lakata
1
Schön und elegant! Sie sind nicht auf die Falle hereingefallen, auf die die anderen hereingefallen sind. Nur noch eine Sache: Ich wette, es gibt einige Beispiele, bei denen die Doppelberechnung aufgrund von Rundungsfehlern unter MAX_LLONG liegt. Mein mathematischer Instinkt sagt mir, dass Sie stattdessen die Differenz zwischen dem doppelten und dem langen Ergebnis berechnen und diese mit MAX_LLONG / 2 oder so vergleichen sollten. Dieser Unterschied besteht aus den Rundungsfehlern der Doppelberechnung und dem Überlauf und sollte normalerweise relativ gering sein, aber in dem von mir erwähnten Fall ist er groß. Aber im Moment bin ich zu faul, um es sicher herauszufinden. :-)
Hans-Peter Störr
9
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

dann

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

Sie können jede Zahl in ein Array schreiben, wobei jedes Element eine Ziffer ist, und die Berechnungen als Polynome durchführen . Nehmen Sie das resultierende Polynom, das ein Array ist, und berechnen Sie das Ergebnis, indem Sie jedes Element des Arrays mit 10 mit der Potenz der Position im Array multiplizieren (wobei die erste Position die größte und die letzte Null ist).

Die Zahl 123kann ausgedrückt werden als:

123 = 100 * 1 + 10 * 2 + 3

für die Sie nur ein Array erstellen [1 2 3].

Sie tun dies für alle Zahlen A, B, C und D und multiplizieren sie dann als Polynome. Sobald Sie das resultierende Polynom haben, rekonstruieren Sie einfach die Zahl daraus.

Mihai
quelle
2
Ich weiß nicht was das ist, aber ich muss es finden. stellen :) . Dies ist eine Lösung für meinen Kopf, während ich mit meiner Freundin einkaufe :)
Mihai
Sie implementieren Bignums in einem base10-Array. GMP ist eine hochwertige Bignum-Bibliothek, die die Basis 4294967296 verwendet. VIEL schneller. Keine Ablehnung, da die Antwort richtig und nützlich ist.
Mooing Duck
Vielen Dank :) . Es ist sinnvoll zu wissen, dass dies eine Möglichkeit ist, aber es gibt bessere Möglichkeiten, also machen Sie es nicht so. Zumindest nicht in dieser Situation :)
Mihai
Wie auch immer ... mit dieser Lösung könnten Sie eine viel größere Zahl als jeder primitive Typ fett schreiben (wie 100-stellige Zahlen) und das Ergebnis als Array behalten. Dies verdient eine Abstimmung: p
Mihai
Ich bin nicht sicher, ob es eine positive Bewertung gibt, da diese Methode (obwohl effektiv und relativ leicht zu verstehen) speicherhungrig und langsam ist.
Mooing Duck
6

Während ein signed long long intWille nicht hält A*B, werden zwei von ihnen. Könnte A*Balso in Baumbegriffe verschiedener Exponenten zerlegt werden, von denen jeder zu einem passt 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;

Gleiches gilt für C*D .

Auf dem geraden Weg könnte die Subtraktion für jedes Paar von AB_iund CD_iebenfalls unter Verwendung eines zusätzlichen Übertragsbits (genau eine 1-Bit-Ganzzahl) für jedes durchgeführt werden. Wenn wir also E = A * BC * D sagen, erhalten Sie so etwas wie:

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

Wir fahren fort, indem wir die obere Hälfte von E_10auf übertragen E_20(um 32 verschieben und hinzufügen, dann die obere Hälfte von löschenE_10 ).

Jetzt können Sie das Übertragsbit entfernen, E_11indem Sie es mit dem richtigen Vorzeichen (erhalten vom Nicht-Übertrags-Teil) zu hinzufügen E_20. Wenn dies einen Überlauf auslöst, passt das Ergebnis auch nicht.

E_10hat jetzt genug 'Platz', um die obere Hälfte von E_00 (Shift, Add, Erase) und dem Carry-Bit zu nehmen E_01.

E_10 kann jetzt wieder größer sein, also wiederholen wir die Übertragung an E_20 .

Zu diesem Zeitpunkt E_20muss Null werden, sonst passt das Ergebnis nicht. Die obere Hälfte vonE_10 ist auch aufgrund der Übertragung leer.

Der letzte Schritt besteht darin, die untere Hälfte von E_20in zu übertragenE_10 wieder.

Wenn die Erwartung, E=A*B+C*Ddie passen würde, signed long long intgilt, haben wir jetzt

E_20=0
E_10=0
E_00=E
Dronus
quelle
1
Dies ist eigentlich die vereinfachte Formel, die man erhalten würde, wenn man die Multiplikationsformel von Ofir verwendet und jedes nutzlose temporäre Ergebnis entfernt.
Dronus
3

Wenn Sie wissen, dass das Endergebnis in Ihrem Integer-Typ darstellbar ist, können Sie diese Berechnung mit dem folgenden Code schnell durchführen. Da der C-Standard angibt, dass vorzeichenlose Arithmetik Modulo-Arithmetik ist und nicht überläuft, können Sie einen vorzeichenlosen Typ verwenden, um die Berechnung durchzuführen.

Der folgende Code setzt voraus, dass es einen vorzeichenlosen Typ mit der gleichen Breite gibt und dass der vorzeichenbehaftete Typ alle Bitmuster zur Darstellung von Werten verwendet (keine Trap-Darstellungen, das Minimum des vorzeichenbehafteten Typs ist das Negative der Hälfte des Moduls des vorzeichenlosen Typs). Wenn dies in einer C-Implementierung nicht zutrifft, können hierfür einfache Anpassungen an der ConvertToSigned-Routine vorgenommen werden.

Im Folgenden wird der Code verwendet signed charund unsigned chardemonstriert. Ändern Sie für Ihre Implementierung die Definition von Signedto typedef signed long long int Signed;und die Definition von Unsignedto 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;
}
Eric Postpischil
quelle
2

Sie können versuchen, die Gleichung in kleinere Komponenten zu zerlegen, die nicht überlaufen.

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

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

    where K = B - N
          J = D - M

Wenn die Komponenten immer noch überlaufen, können Sie sie rekursiv in kleinere Komponenten aufteilen und dann neu kombinieren.

Bradgonesurfing
quelle
Dies mag richtig sein oder auch nicht, ist aber definitiv verwirrend. Sie definieren Kund J, warum nicht Nund M. Ich denke auch, dass Sie die Gleichung in größere Teile zerlegen . Da Ihr Schritt 3 die gleiche ist wie die Frage des OP, außer komplizierter (AK-CJ)->(AB-CD)
Mooing Duck
N wird von nichts vereinfacht. Es ist nur eine Zahl, die von A abgezogen wird, um sie kleiner zu machen. Eigentlich ist es eine ähnliche, aber schlechtere Lösung als Paquetp. Hier verwende ich Subtraktion anstelle von Integer Division, um es kleiner zu machen.
Bradgonesurfing
2

Ich habe möglicherweise nicht alle Randfälle abgedeckt oder dies rigoros getestet, aber dies implementiert eine Technik, die ich in den 80er Jahren verwendet habe, als ich versucht habe, 32-Bit-Ganzzahlmathematik auf einer 16-Bit-CPU durchzuführen. Im Wesentlichen teilen Sie die 32 Bit in zwei 16-Bit-Einheiten auf und arbeiten separat mit ihnen.

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

  }

}

Drucke:

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

das sieht für mich so aus, als ob es funktioniert.

Ich wette, ich habe einige Feinheiten wie das Beobachten von Zeichenüberläufen usw. übersehen, aber ich denke, die Essenz ist da.

OldCurmudgeon
quelle
1
Ich denke, dies ist eine Implementierung dessen, was @Ofir vorgeschlagen hat.
OldCurmudgeon
2

Der Vollständigkeit halber stellen Ihnen einige Compiler (z. B. GCC) heutzutage eine 128-Bit-Ganzzahl zur Verfügung, da dies von niemandem erwähnt wurde.

Eine einfache Lösung könnte also sein:

(long long)((__int128)A * B - (__int128)C * D)
i Code 4 Essen
quelle
1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Weder B/Cnoch D/Akann überlaufen, also (B/C-D/A)zuerst berechnen . Da das Endergebnis gemäß Ihrer Definition nicht überläuft, können Sie die verbleibenden Multiplikationen sicher durchführen und berechnen(B/C-D/A)*A*C welches das erforderliche Ergebnis ist.

Beachten Sie, dass, wenn Ihre Eingabe auch extrem klein sein kann , die B/Coder D/Aüberlaufen kann. Wenn es möglich ist, sind laut Eingangskontrolle möglicherweise komplexere Manipulationen erforderlich.

SomeWittyUsername
quelle
2
Das wird nicht funktionieren, da die Ganzzahldivision Informationen verliert (der Bruchteil des Ergebnisses)
Ofir
@Ofir das stimmt, aber du kannst den Kuchen nicht essen und ihn unberührt lassen. Sie müssen entweder präzise oder mit zusätzlichen Ressourcen bezahlen (wie Sie in Ihrer Antwort vorgeschlagen haben). Meine Antwort ist mathematischer Natur, während Ihre Antwort computerorientiert ist. Jeder kann je nach den Umständen richtig sein.
SomeWittyUsername
2
Sie haben Recht - ich hätte es so formulieren sollen - wird kein genaues Ergebnis liefern, anstatt nicht zu funktionieren, da die Mathematik korrekt ist. Beachten Sie jedoch, dass in den Fällen, die den Fragesteller wahrscheinlich interessieren (z. B. im Beispiel in der Frage), der Fehler wahrscheinlich überraschend groß ist - viel größer, als es für eine praktische Anwendung akzeptabel ist. Auf jeden Fall - es war eine aufschlussreiche Antwort und ich hätte diese Sprache nicht verwenden sollen.
Ofir
@Ofir Ich glaube nicht, dass deine Sprache unangemessen war. Das OP forderte eindeutig eine "korrekte" Berechnung, die nicht an Genauigkeit verlieren würde, um unter extremen Ressourcenbeschränkungen durchgeführt zu werden.
user4815162342
1

Wählen K = a big number (zB. K = A - sqrt(A))

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

Warum?

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

Beachten Sie, dass A, B, C und D große Zahlen sind A-Cund B-Ddaher kleine Zahlen.

Amir Saniyan
quelle
Wie wählst du K in der Praxis? Außerdem kann K * (A-C + BD) immer noch überlaufen.
ylc
@ylc: Wähle K = sqrt (A), nicht das A-C+B-Dist eine kleine Zahl. Da A, B, C und D große Zahlen sind, ist AC eine kleine Zahl.
Amir Saniyan
Wenn Sie K = sqrt (A) wählen , kann (AK) * (BK) erneut überlaufen.
ylc
@ylc: OK! Ich ändere es auf A - sqrt(A):)
Amir Saniyan
Dann kann K * (A-C + BD) überlaufen.
ylc