Fang-und compute-überlauf bei der Multiplikation von zwei großen Ganzzahlen
Ich bin auf der Suche nach einem effizienten (wahlweise standard, elegant und einfach zu implementieren) Lösung zu vermehren, relativ große zahlen und speichert das Ergebnis in einer oder mehreren Ganzzahlen :
Lassen Sie sagen, ich habe zwei 64-bit-Ganzzahlen deklariert wie diese :
uint64_t a = xxx, b = yyy;
Wenn ich a * b
, wie kann ich erkennen, ob die Messergebnisse in einem überlauf-und in diesem Fall speichern Sie die tragen irgendwo?
Bitte beachten Sie, dass möchte ich nicht verwenden, keine große Nummer Bibliothek da habe ich die Beschränkungen auf die Art und Weise, die Speichere ich die zahlen.
- Streng von der C standard-text, der vorzeichenlose integer-Multiplikation kann nicht überlaufen, aber es kann umschlagen. Das Verhalten von signed integer overflow nicht definiert ist. Es gibt Antworten auf diese Frage, die strikt davon ausgehen, dass die Operanden sind vorzeichenlose, und kann nicht als solche verwendet werden, die für Ganzzahlen mit Vorzeichen.
Du musst angemeldet sein, um einen Kommentar abzugeben.
1. Erkennung von überlauf:
Edit: Fixed division durch
0
(danke Mark!)2. Computing das tragen ist ziemlich beteiligt. Ein Ansatz HIERFÜR ist die split werden beide Operanden in halben Worten, dann bewerben Sie sich lange Multiplikation um die Hälfte-Wörter:
Zu sehen, dass keine der partiellen Summen selbst überlauf, betrachten wir den schlimmsten Fall:
Lassen
B = 1 << 32
. Wir haben dannIch glaube, das wird funktionieren - zumindest ist es verarbeitet Sjlver test-Fall. Abgesehen davon, dass es nicht getestet ist (und vielleicht nicht einmal kompilieren, da ich nicht über ein C++ - compiler zur hand mehr).
>>>
konstruieren tun? Weder>> >
noch> >>
Sinn macht... ich denke mal es ist ein Tippfehler?warning: left shift count >= width of type
für((1 << 32) - 1) & x
?a * b / a != b
erfasst alle Fälle von überlaufen?a * b
überläuft. Danna * b ≤ ab - 2^64
und dahera * b / a ≤ ab/a - (2^64/a) < b
.s3
unds2
? Wie es scheint, können Sie nur zuweisenx
an diesem Punkt zum tragen.Die Idee ist folgende Tatsache, die wahr ist für den integralen Betrieb:
a*b > c
wenn und nur wenna > c/b
/
ist ganzzahligen division hier.Den pseudocode zu prüfen, gegen überlauf für positive zahlen folgt:
if (a > max_int64 /b), dann "überlauf", sonst "ok".
Behandeln Nullen und negativen zahlen, die Sie hinzufügen sollten mehr Kontrollen.
C-code für nicht-negative
a
undb
folgt:Hinweis:
Zu berechnen, tragen wir verwenden können Ansatz-split Anzahl in zwei 32-Ziffern und multiplizieren Sie Sie, wie wir dies auf dem Papier. Wir müssen die split-zahlen zu vermeiden, überlauf.
Code folgt:
+
Operationen können überlaufen...c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;
erzeugt die falsche tragen. Sowas brauchtc1 = d11 + d12; if (c1 < d11) c1 = c1 >> 32 + 0x100000000u; else c1 >>= 32;
Obwohl es schon einige andere Antworten auf diese Frage, ich, einige von Ihnen haben code ist komplett ungetestet, und so weit niemand hat angemessen im Vergleich der verschiedenen möglichen Optionen.
Aus diesem Grund habe ich geschrieben und getestet mehrere mögliche Implementierungen (die Letzte ist, basierend auf dieser code von OpenBSD, diskutiert auf Reddit hier). Hier ist der code:
Hier sind die Ergebnisse der Tests mit verschiedenen Compilern und Systemen ich habe (in diesem Fall werden alle Tests durchgeführt, die auf OS X, aber die Ergebnisse sollten ähnlich sein wie bei BSD oder Linux Systeme):
Basierend auf diesen Ergebnissen, können wir ziehen ein paar Folgerungen:
Einer version, die auch funktioniert, wenn a == 0:
a
,b
undx
Teile cast zu unsigned, sonst ist der compiler frei, optimieren sich diex / a
Teil.x / a != b
zu(a*b) / a != b
aufgrund der Zuweisung Anweisung nur vor, das reduziert sich dann aufb != b
und schließlich einefalse
. Das ist, wie die überlauf-Prüfung kann nicht funktionieren, aufgrund der compiler-Optimierung. ...(uintmax_t) x / a != b
oder-fwrapv
flag auf den compiler anzunehmen, dass das overflow-Verhalten.Wenn Sie nicht nur brauchen, um zu erkennen, überlauf, sondern auch zu erfassen, die tragen, sind Sie am besten aus, dass Sie Ihre zahlen nach unten in die 32-bit-Teile. Der code ist ein Alptraum; was folgt, ist nur eine Skizze:
Das problem ist nicht nur die teilweise Produkte, sondern die Tatsache, dass jede der Summen kann überlaufen.
Wenn ich hatte dies für real, ich würde schreiben Sie eine extended-multiplizieren routine in der lokalen assembly language. Das heißt, zum Beispiel, multiplizieren von zwei 64-bit-Ganzzahlen um eine 128-bit-Ergebnis, das gespeichert ist, in zwei 64-bit-Register. Alle vernünftigen hardware bietet diese Funktionalität in einer einzigen nativen multiplizieren Unterricht—es ist nicht nur zugänglich von C.
Dies ist einer jener seltenen Fälle, in denen die Lösung, die am meisten elegante und einfach zu Programmieren ist eigentlich assembly-Sprache verwenden. Aber es ist sicherlich nicht tragbar 🙁
Vielleicht der beste Weg, um dieses problem zu lösen, ist eine Funktion, die multipliziert zwei UInt64 und Ergebnisse ein paar UInt64, einem oberen Teil und einem unteren Teil des UInt128 Ergebnis. Hier ist die Lösung, sowie eine Funktion, die zeigt das Ergebnis in hex. Ich denke, dass Sie Sie vielleicht lieber eine C++ Lösung, aber ich habe einen Swift-Lösung, die zeigt, wie die Verwaltung das problem:
Hier ist ein Beispiel, um zu überprüfen, dass die Funktion funktioniert:
Habe ich daran gearbeitet, dieses problem dieser Tage und ich muss sagen, es hat mich beeindruckt die Anzahl der Zeiten ich habe gesehen, Menschen, die sagen, der beste Weg, zu wissen, ob es ein überlauf ist, teilen Sie das Resultat, das ist völlig ineffizient und unnötig. Der Punkt für diese Funktion ist, dass es sein muss, so schnell wie möglich.
Gibt es zwei Optionen für die überlauf-Erkennung:
1º - Wenn möglich, erstellen der Ergebnis-variable als doppelt so groß wie die Multiplikatoren, zum Beispiel:
Wissen Sie unseren Kurs, wenn es zu einem überlauf, und der code ist am schnellsten möglich, ohne dass das schreiben in Maschinen-code. Je nach compiler dieser code kann verbessert werden, in Maschinen-code.
2º - Ist unmöglich zu schaffen, eine Ergebnis-variable zweimal so groß wie die Multiplikatoren variable:
Dann sollten Sie spielen mit, wenn die Bedingungen zu bestimmen, den besten Weg. Weiter mit dem Beispiel:
Ich hoffe, dieser code hilft Sie haben ein sehr effizientes Programm, und ich hoffe, der code ist klar,, wenn nicht, werde ich einige coments.
beste Grüße.
Ist hier ein trick für die Erkennung, ob die Multiplikation zweier vorzeichenloser Integer-overflows.
Machen wir die Beobachtung, dass, wenn wir multiplizieren ein N-bit Breite binäre Zahl mit einem M-bit Breite binäre Zahl ist das Produkt nicht mehr als N + M-bits.
Zum Beispiel, wenn wir gefragt werden, zu vermehren, eine drei-bit-Zahl mit einer zwanzig-neun-bit-Zahl, wir wissen, dass diese nicht überlauf zweiunddreißig bits.
Ein paar tests: (auf 64-bit-system):
Schritte in
might_be_mul_oflow
sind fast sicher langsamer als nur tun, den Geschäftsbereich test, zumindest auf mainstream-Prozessoren in desktop-PCs, Server und mobile Geräte. Auf chips ohne gute Aufteilung-Unterstützung, könnte es nützlich sein.Fällt mir ein, dass es einen anderen Weg, das zu tun diese frühe Ablehnung testen.
Beginnen wir mit ein paar zahlen
arng
undbrng
die initialisiert werden0x7FFF...FFFF
und1
.Wenn
a <= arng
undb <= brng
können wir schließen, dass es keinen überlauf.Ansonsten verschieben wir
arng
rechts und shiftbrng
auf der linken Seite, hinzufügen, ein bisschen zubrng
, so dass Sie0x3FFF...FFFF
und3
.Wenn
arng
null ist, fertig; ansonsten gehen Sie am 2.Die Funktion jetzt aussieht:
Einfach und schnell mit clang und gcc:
Dies wird mit hardware-Unterstützung für überlauf-Erkennung verfügbar. Durch compiler-Erweiterungen kann es auch handhaben-Ganzzahl-überlauf (ersetzen umul mit smul), eventhough das ist Undefiniertes Verhalten in C++.
Wenn Sie nur wollen, um zu erkennen, überlauf, wie Sie über das konvertieren zu verdoppeln, dabei die Vermehrung und wenn
|x| < 2^53, konvertieren int64
|x| < 2^63, machen die Multiplikation mit int64
sonst produzieren was auch immer-Fehler, den Sie haben wollen?
Diese scheint zu funktionieren:
2 << 53
Sie verlieren so viel Präzision, dass benachbarte doubles sind jetzt größer als 1 auseinander. Zahlen springen aus(2 << 53) + 0
zu(2 << 53) + 2
. Wenn Sie konvertierenuint64_t
zudouble
Sie möglicherweise Rundung nach unten. Der Wertdx
ist das Produkt von zwei potenziell abgerundete zahlen. Daher gibt es keine Garantie Vergleich zuMAX_INT
sagt Sie etwas sinnvolles über die Multiplikation der ursprünglichen Werte.