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

Lesezeit: 8 Minuten

Benutzeravatar von NGix
NGix

Ich muss einen Ausdruck berechnen, der so aussieht:
A*B - C*Dwo ihre Typen sind: signed long long int A, B, C, D;
Jede Zahl kann wirklich groß sein (ohne ihren Typ zu überschreiten). Während A*B könnte Überlauf verursachen, gleichzeitig Ausdruck A*B - C*D kann wirklich klein sein. Wie kann ich es richtig berechnen?

Zum Beispiel: MAX * MAX - (MAX - 1) * (MAX + 1) == 1wo MAX = LLONG_MAX - n und n – irgendeine natürliche Zahl.

  • Wie wichtig ist Genauigkeit?

    – Anirudh Ramanathan

    5. November 2012 um 17:22 Uhr

  • @Cthulhu, tolle Frage. Er könnte versuchen, eine äquivalente Funktion mit einer kleineren Zahl zu erstellen, indem er sie alle durch 10 oder so dividiert und dann das Ergebnis multipliziert.

    – Chris

    5. November 2012 um 17:23 Uhr

  • Die Vars A, B, C, D sind signiert. Dies impliziert A - C überlaufen könnte. Ist das ein Problem, das Sie berücksichtigen sollten, oder wissen Sie, dass dies mit Ihren Daten nicht passieren wird?

    – William Morris

    5. November 2012 um 17:47 Uhr

  • @MooingDuck, aber Sie können vorher prüfen, ob die Operation stackoverflow.com/a/3224630/158285 überläuft

    – Bradgonesurfen

    5. November 2012 um 18:24 Uhr

  • @Chris: Nein, ich sage, dass es keine tragbare Möglichkeit gibt, zu überprüfen, ob ein signierter Überlauf aufgetreten ist. (Brad hat Recht, dass Sie das tragbar erkennen können Wille passieren). Die Verwendung der Inline-Assemblierung ist eine von vielen nicht-portablen Möglichkeiten zur Überprüfung.

    – Muhende Ente

    5. November 2012 um 18:31 Uhr

Benutzeravatar von Anirudh Ramanathan
Anirudh Ramanathan

Das erscheint mir zu banal. Aber A*B ist derjenige, der überlaufen könnte.

Sie könnten Folgendes tun, ohne an Genauigkeit 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 Zerlegung kann sein weiter gemacht.
Wie @Gian betonte, muss während des Subtraktionsvorgangs möglicherweise darauf geachtet werden, wenn der Typ unsigned long long ist.


Bei dem Fall, den Sie in der Frage haben, dauert es beispielsweise nur eine Iteration,

 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

  • @Caleb, wenden Sie einfach denselben Algorithmus an C*D

    – Chris

    5. November 2012 um 17:35 Uhr


  • Ich denke, Sie sollten erklären, was E darstellt.

    – Kaleb

    5. November 2012 um 17:37 Uhr

  • Sowohl long long als auch double sind 64 Bit. Da Double einige Bits für den Exponenten allokieren muss, hat es a kleiner Bereich möglicher Werte ohne Genauigkeitsverlust.

    – Jim Garnison

    5. November 2012 um 19:03 Uhr


  • @Cthulhu – scheint mir so, als würde dies nur funktionieren, wenn alle Zahlen sehr groß sind … zum Beispiel hätten Sie immer noch einen Überlauf mit {A, B, C, D} = {MAX, MAX, MAX, 2}. Das OP sagt “Jede Zahl kann wirklich groß sein”, aber aus der Problemstellung geht nicht hervor, dass jede Zahl muss richtig groß sein.

    – Kevin K

    5. November 2012 um 21:28 Uhr

  • Was ist, wenn einer von A,B,C,D sind aber negativ? Gewohnheit E oder F dann noch größer sein?

    – Super

    6. November 2012 um 14:36 ​​Uhr

Benutzeravatar von Ofir
Ofir

Die einfachste und allgemeinste Lösung besteht darin, eine Darstellung zu verwenden, die nicht überlaufen kann, entweder durch Verwendung einer Bibliothek mit langen Ganzzahlen (z http://gmplib.org/) oder die Verwendung einer Struktur oder eines Arrays darstellen und eine Art lange Multiplikation implementieren (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

Unter der Annahme, dass das Endergebnis in 64 Bit passt, benötigen Sie eigentlich nicht wirklich die meisten Bits von R3 und keines von R4

  • Die obige Berechnung ist wirklich nicht so kompliziert, wie sie aussieht, es ist wirklich eine einfache lange Multiplikation zur Basis 2^32, und der Code in C sollte einfacher aussehen. Außerdem ist es eine gute Idee, generische Funktionen zu erstellen, um diese Arbeit in Ihrem Programm zu erledigen.

    – Ofir

    5. November 2012 um 17:44 Uhr

Benutzeravatar von RiaD
RiaD

Beachten Sie, dass dies kein Standard ist, da es auf Wrap-Around-Signed-Overflow angewiesen ist. (GCC hat Compiler-Flags, die dies ermöglichen.)

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


Hier ist eine Problemumgehung, die sich nur auf das implementierungsdefinierte Verhalten des Umwandelns einer Ganzzahl ohne Vorzeichen in eine Ganzzahl mit Vorzeichen stützt. 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)

Dies wirft die Eingaben auf unsigned long long wobei das Überlaufverhalten durch den Standard garantiert umlaufend ist. Das Zurücksetzen auf eine vorzeichenbehaftete Ganzzahl am Ende ist der von der Implementierung definierte Teil, funktioniert aber heute in fast allen Umgebungen.


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

  • +1 Du bist der Einzige, dem das auffällt. Der einzige knifflige Teil besteht darin, den Compiler so einzustellen, dass er einen Wrap-Around-Überlauf ausführt, und zu prüfen, ob das richtige Ergebnis tatsächlich in a passt long long.

    – Mystisch

    5. November 2012 um 18:27 Uhr

  • Selbst die naive Version ohne jegliche Tricks wird das Richtige tun die meisten Implementierungen; der Standard garantiert dies nicht, aber Sie müssten eine 1er-Komplement-Maschine oder ein anderes ziemlich seltsames Gerät finden, damit es fehlschlägt.

    – Hobbs

    6. November 2012 um 2:23 Uhr

  • Ich denke, das ist eine wichtige Antwort. Ich stimme zu, dass es möglicherweise keine korrekte Programmierung ist, 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 auf dieses Verhalten für Festkommafilterimplementierungen, für die die akzeptierte Antwort eine inakzeptable Leistung haben wird.

    – Peter M

    16. Mai 2013 um 16:39 Uhr

Benutzeravatar von paquetp
paquetp

Das sollte funktionieren (glaube 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 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)

landrys Benutzeravatar
landwirtschaft

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

Gians Benutzer-Avatar
Gian

Sie könnten erwägen, einen größten gemeinsamen Faktor für alle Ihre Werte zu berechnen und sie dann durch diesen Faktor zu dividieren, bevor Sie Ihre arithmetischen Operationen durchführen, und dann erneut multiplizieren. Dies setzt jedoch voraus, dass ein solcher Faktor existiert (z. B. wenn A, B, C und D zufällig relativ teilerfremd sind, haben sie keinen gemeinsamen Teiler).

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

Benutzeravatar von Esteban Crespi
Esteban Crespi

Wenn das Ergebnis in ein long long int passt, ist der Ausdruck A*BC*D in Ordnung, da er die Arithmetik mod 2^64 ausführt und das richtige Ergebnis liefert. Das Problem besteht darin, zu wissen, ob das Ergebnis in ein long long int passt. Um dies zu erkennen, können Sie den folgenden Trick mit Doubles anwenden:

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

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

  • 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

    5. November 2012 um 23:32 Uhr

  • Schön und elegant! Du bist nicht in die Falle getappt, in die die anderen reingefallen sind. Nur noch etwas: Ich wette, es gibt einige Beispiele, bei denen die doppelte Berechnung nur aufgrund von Rundungsfehlern unter MAX_LLONG liegt. Mein mathematischer Instinkt sagt mir, dass Sie stattdessen die Differenz des doppelten und des langen Ergebnisses berechnen und das mit MAX_LLONG/2 oder so vergleichen sollten. Diese Differenz sind die Rundungsfehler der doppelten Berechnung und zuzüglich des Überlaufs und sollten normalerweise relativ gering sein, aber in dem von mir erwähnten Fall wird sie groß sein. Aber im Moment bin ich zu faul, es genau herauszufinden. 🙂

    – Hans-Peter Störr

    12. November 2012 um 8:18 Uhr


1424720cookie-checkSo vermeiden Sie einen Überlauf in expr. A B C D

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy