Es ist alles eine Frage des angemessenen Speichers und der Algorithmen, um Zahlen als kleinere Teile zu behandeln. Nehmen wir an, Sie haben einen Compiler, in dem ein int
nur 0 bis 99 sein kann, und Sie möchten Zahlen bis 999999 verarbeiten (wir werden uns hier nur um positive Zahlen kümmern, um es einfach zu halten).
Sie tun dies, indem Sie jeder Zahl drei int
Sekunden geben und dieselben Regeln anwenden, die Sie in der Grundschule für Addition, Subtraktion und die anderen Grundoperationen gelernt haben (sollten).
In einer Bibliothek mit beliebiger Genauigkeit gibt es keine feste Begrenzung für die Anzahl der Basistypen, die zur Darstellung unserer Zahlen verwendet werden, unabhängig davon, welchen Speicher der Speicher aufnehmen kann.
Ergänzung zum Beispiel 123456 + 78
::
12 34 56
78
-- -- --
12 35 34
Arbeiten vom am wenigsten signifikanten Ende:
- anfänglicher Übertrag = 0.
- 56 + 78 + 0 Übertrag = 134 = 34 mit 1 Übertrag
- 34 + 00 + 1 Übertrag = 35 = 35 mit 0 Übertrag
- 12 + 00 + 0 Übertrag = 12 = 12 mit 0 Übertrag
Auf diese Weise funktioniert das Hinzufügen im Allgemeinen auf Bitebene in Ihrer CPU.
Die Subtraktion ist ähnlich (unter Verwendung der Subtraktion des Basistyps und Ausleihen anstelle des Übertrags), die Multiplikation kann mit wiederholten Additionen (sehr langsam) oder Kreuzprodukten (schneller) erfolgen, und die Division ist schwieriger, kann jedoch durch Verschieben und Subtrahieren der Zahlen erfolgen beteiligt (die lange Teilung, die Sie als Kind gelernt hätten).
Ich habe tatsächlich Bibliotheken geschrieben, um diese Art von Dingen mit den maximalen Zehnerpotenzen zu erledigen, die im Quadrat in eine Ganzzahl eingepasst werden können (um einen Überlauf beim Multiplizieren von zwei int
s zu verhindern , z. B. wenn ein 16-Bit int
auf 0 bis 99 begrenzt ist Generieren Sie 9.801 (<32.768) im Quadrat oder 32-Bit int
mit 0 bis 9.999, um 99.980.001 (<2.147.483.648) zu generieren, was die Algorithmen erheblich vereinfacht.
Einige Tricks, auf die Sie achten sollten.
1 / Wenn Sie Zahlen hinzufügen oder multiplizieren, weisen Sie den maximal benötigten Speicherplatz vorab zu und reduzieren Sie ihn später, wenn Sie feststellen, dass er zu groß ist. Wenn Sie beispielsweise zwei 100-stellige int
Zahlen (wobei die Ziffer eine ist ) hinzufügen , erhalten Sie nie mehr als 101 Ziffern. Das Multiplizieren einer 12-stelligen Zahl mit einer 3-stelligen Zahl erzeugt niemals mehr als 15 Stellen (addieren Sie die Ziffernzahlen).
2 / Um die Geschwindigkeit zu erhöhen, normalisieren (reduzieren Sie den dafür erforderlichen Speicherplatz) die Nummern nur, wenn dies unbedingt erforderlich ist. Meine Bibliothek hatte dies als separaten Aufruf, damit der Benutzer zwischen Geschwindigkeits- und Speicherproblemen entscheiden kann.
3 / Die Addition einer positiven und einer negativen Zahl ist eine Subtraktion, und die Subtraktion einer negativen Zahl entspricht der Addition der äquivalenten positiven Zahl. Sie können eine Menge Code sparen, indem Sie sich nach dem Anpassen der Vorzeichen gegenseitig aufrufen und subtrahieren lassen.
4 / Vermeiden Sie es, große Zahlen von kleinen zu subtrahieren, da Sie immer Zahlen wie:
10
11-
-- -- -- --
99 99 99 99 (and you still have a borrow).
Subtrahieren Sie stattdessen 10 von 11 und negieren Sie es dann:
11
10-
--
1 (then negate to get -1).
Hier sind die Kommentare (in Text umgewandelt) aus einer der Bibliotheken, für die ich dies tun musste. Der Code selbst ist leider urheberrechtlich geschützt, aber Sie können möglicherweise genügend Informationen auswählen, um die vier grundlegenden Vorgänge auszuführen. Nehmen Sie im Folgenden an, dass -a
und -b
negative Zahlen darstellen und a
und b
Null oder positive Zahlen sind.
Für hinaus , wenn Anzeichen sind unterschiedlich, Verwendung Subtraktion der Negation:
-a + b becomes b - a
a + -b becomes a - b
Für Subtraktion , wenn Anzeichen sind unterschiedlich, Verwendung Zugabe der Negation:
a - -b becomes a + b
-a - b becomes -(a + b)
Auch spezielle Behandlung, um sicherzustellen, dass wir kleine Zahlen von großen subtrahieren:
small - big becomes -(big - small)
Bei der Multiplikation wird die Einstiegsmathematik wie folgt verwendet:
475(a) x 32(b) = 475 x (30 + 2)
= 475 x 30 + 475 x 2
= 4750 x 3 + 475 x 2
= 4750 + 4750 + 4750 + 475 + 475
Die Art und Weise, wie dies erreicht wird, besteht darin, jede der Ziffern von 32 einzeln (rückwärts) zu extrahieren und dann mit add einen Wert zu berechnen, der dem Ergebnis hinzugefügt werden soll (anfänglich Null).
ShiftLeft
und ShiftRight
Operationen werden verwendet, um a schnell LongInt
mit dem Wrap-Wert zu multiplizieren oder zu dividieren (10 für "echte" Mathematik). Im obigen Beispiel addieren wir 475 zweimal zu Null (die letzte Ziffer von 32), um 950 zu erhalten (Ergebnis = 0 + 950 = 950).
Dann haben wir die Schicht 475 verlassen, um 4750 zu erhalten, und die rechte Schicht 32, um 3 zu erhalten. Addiere 4750 dreimal zu Null, um 14250 zu erhalten, und addiere dann zum Ergebnis von 950, um 15200 zu erhalten.
Linksverschiebung 4750, um 47500 zu erhalten, Rechtsverschiebung 3, um 0 zu erhalten. Da die rechtsverschobene 32 jetzt Null ist, sind wir fertig und tatsächlich entspricht 475 x 32 15200.
Die Division ist ebenfalls schwierig, basiert jedoch auf früher Arithmetik (die "Gazinta" -Methode für "geht hinein"). Betrachten Sie die folgende lange Unterteilung für 12345 / 27
:
457
+-------
27 | 12345 27 is larger than 1 or 12 so we first use 123.
108 27 goes into 123 4 times, 4 x 27 = 108, 123 - 108 = 15.
---
154 Bring down 4.
135 27 goes into 154 5 times, 5 x 27 = 135, 154 - 135 = 19.
---
195 Bring down 5.
189 27 goes into 195 7 times, 7 x 27 = 189, 195 - 189 = 6.
---
6 Nothing more to bring down, so stop.
Daher 12345 / 27
ist 457
mit Rest 6
. Überprüfen:
457 x 27 + 6
= 12339 + 6
= 12345
Dies wird implementiert, indem eine Drawdown-Variable (anfänglich Null) verwendet wird, um die Segmente von 12345 einzeln zu senken, bis sie größer oder gleich 27 sind.
Dann subtrahieren wir einfach 27 davon, bis wir unter 27 kommen - die Anzahl der Subtraktionen ist das Segment, das der obersten Zeile hinzugefügt wird.
Wenn es keine Segmente mehr zu stürzen gibt, haben wir unser Ergebnis.
Denken Sie daran, dies sind ziemlich grundlegende Algorithmen. Es gibt weitaus bessere Möglichkeiten, komplexe Arithmetik durchzuführen, wenn Ihre Zahlen besonders groß sein sollen. Sie können sich so etwas wie die GNU Multiple Precision Arithmetic Library ansehen - sie ist wesentlich besser und schneller als meine eigenen Bibliotheken.
Es hat die eher unglückliche Fehlfunktion, dass es einfach beendet wird, wenn der Speicher knapp wird (ein ziemlich schwerwiegender Fehler für eine Allzweckbibliothek meiner Meinung nach), aber wenn Sie darüber hinausblicken können, ist es ziemlich gut darin, was es tut.
Wenn Sie es aus Lizenzgründen nicht verwenden können (oder weil Sie nicht möchten, dass Ihre Anwendung ohne ersichtlichen Grund beendet wird), können Sie zumindest die Algorithmen von dort für die Integration in Ihren eigenen Code erhalten.
Ich habe auch festgestellt, dass die Köpfe bei MPIR (eine Gabelung von GMP) für Diskussionen über mögliche Änderungen zugänglicher sind - sie scheinen entwicklerfreundlicher zu sein.