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*B
dies zu einem Überlauf führen kann, A*B - C*D
kann 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 - n
und n - eine natürliche Zahl.
c++
c
integer-overflow
NGix
quelle
quelle
A - C
möglichen Überlauf. Ist das ein zu berücksichtigendes Problem oder wissen Sie, dass dies mit Ihren Daten nicht passieren wird?Antworten:
Das scheint mir zu trivial. Aber
A*B
ist derjenige, der überlaufen könnte.Sie können Folgendes tun, ohne an Präzision zu verlieren
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.
quelle
C*D
A,B,C,D
einige davon negativ sind? WirdE
oder wirdF
es dann nicht noch größer?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:
Angenommen, das Endergebnis passt in 64 Bit, dann benötigen Sie die meisten Bits von R3 und keines von R4 nicht wirklich
quelle
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 passtlong 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.
Dadurch werden die Eingaben dahin verschoben,
unsigned long long
wo 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.
quelle
long long
.Das sollte funktionieren (denke ich):
Hier ist meine Ableitung:
quelle
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
,C
undD
passieren 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.
quelle
long double
verfügbar. In diesem Fall kann ein akzeptables Maß an Präzision erreicht werden (und das Ergebnis kann gerundet werden).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:
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.
quelle
dann
quelle
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
123
kann ausgedrückt werden als: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.
quelle
Während ein
signed long long int
Wille nicht hältA*B
, werden zwei von ihnen. KönnteA*B
also in Baumbegriffe verschiedener Exponenten zerlegt werden, von denen jeder zu einem passtsigned long long int
.Gleiches gilt für
C*D
.Auf dem geraden Weg könnte die Subtraktion für jedes Paar von
AB_i
undCD_i
ebenfalls 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:Wir fahren fort, indem wir die obere Hälfte von
E_10
auf übertragenE_20
(um 32 verschieben und hinzufügen, dann die obere Hälfte von löschenE_10
).Jetzt können Sie das Übertragsbit entfernen,
E_11
indem Sie es mit dem richtigen Vorzeichen (erhalten vom Nicht-Übertrags-Teil) zu hinzufügenE_20
. Wenn dies einen Überlauf auslöst, passt das Ergebnis auch nicht.E_10
hat jetzt genug 'Platz', um die obere Hälfte vonE_00
(Shift, Add, Erase) und dem Carry-Bit zu nehmenE_01
.E_10
kann jetzt wieder größer sein, also wiederholen wir die Übertragung anE_20
.Zu diesem Zeitpunkt
E_20
muss 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_20
in zu übertragenE_10
wieder.Wenn die Erwartung,
E=A*B+C*D
die passen würde,signed long long int
gilt, haben wir jetztquelle
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 char
undunsigned char
demonstriert. Ändern Sie für Ihre Implementierung die Definition vonSigned
totypedef signed long long int Signed;
und die Definition vonUnsigned
totypedef unsigned long long int Unsigned;
.quelle
Sie können versuchen, die Gleichung in kleinere Komponenten zu zerlegen, die nicht überlaufen.
Wenn die Komponenten immer noch überlaufen, können Sie sie rekursiv in kleinere Komponenten aufteilen und dann neu kombinieren.
quelle
K
undJ
, warum nichtN
undM
. 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)
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.
Drucke:
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.
quelle
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:
quelle
AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C
. WederB/C
nochD/A
kann ü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/C
oderD/A
überlaufen kann. Wenn es möglich ist, sind laut Eingangskontrolle möglicherweise komplexere Manipulationen erforderlich.quelle
Wählen
K = a big number
(zB.K = A - sqrt(A)
)Warum?
Beachten Sie, dass A, B, C und D große Zahlen sind
A-C
undB-D
daher kleine Zahlen.quelle
A-C+B-D
ist eine kleine Zahl. Da A, B, C und D große Zahlen sind, ist AC eine kleine Zahl.A - sqrt(A)
:)