Ich möchte eine große int-Klasse in C ++ als Programmierübung implementieren - eine Klasse, die Zahlen verarbeiten kann, die größer als eine lange int sind. Ich weiß, dass es bereits mehrere Open Source-Implementierungen gibt, aber ich würde gerne meine eigenen schreiben. Ich versuche ein Gefühl dafür zu bekommen, was der richtige Ansatz ist.
Ich verstehe, dass die allgemeine Strategie darin besteht, die Zahl als Zeichenfolge abzurufen, sie dann in kleinere Zahlen (z. B. einzelne Ziffern) aufzuteilen und sie in einem Array zu platzieren. Zu diesem Zeitpunkt sollte es relativ einfach sein, die verschiedenen Vergleichsoperatoren zu implementieren. Mein Hauptanliegen ist, wie ich Dinge wie Addition und Multiplikation implementieren würde.
Ich suche einen allgemeinen Ansatz und Rat im Gegensatz zum eigentlichen Arbeitscode.
quelle
Antworten:
Dinge, die für eine große int-Klasse zu beachten sind:
Mathematische Operatoren: +, -, /, *,% Vergessen Sie nicht, dass sich Ihre Klasse auf beiden Seiten des Operators befindet, dass die Operatoren verkettet werden können, dass einer der Operanden ein int, float, double usw. Sein kann .
E / A-Operatoren: >>, << Hier erfahren Sie, wie Sie Ihre Klasse ordnungsgemäß aus Benutzereingaben erstellen und auch für die Ausgabe formatieren.
Konvertierungen / Casts: Finden Sie heraus, in welche Typen / Klassen Ihre big int-Klasse konvertierbar sein soll und wie Sie die Konvertierung richtig handhaben. Eine Schnellliste würde double und float enthalten und kann int (mit ordnungsgemäßer Überprüfung der Grenzen) und complex (vorausgesetzt, sie kann den Bereich verarbeiten) enthalten.
quelle
operator<<
undoperator>>
mitiostream
s für E / A.Eine lustige Herausforderung. :) :)
Ich gehe davon aus, dass Sie Ganzzahlen beliebiger Länge wollen. Ich schlage folgenden Ansatz vor:
Betrachten Sie die binäre Natur des Datentyps "int". Denken Sie daran, einfache Binäroperationen zu verwenden, um zu emulieren, was die Schaltkreise in Ihrer CPU tun, wenn sie Dinge hinzufügen. Wenn Sie ausführlicher interessiert sind, lesen Sie diesen Wikipedia-Artikel über Halb- und Volladdierer . Sie werden etwas Ähnliches tun, aber Sie können so niedrig wie das sein - aber da ich faul bin, dachte ich, ich würde einfach darauf verzichten und eine noch einfachere Lösung finden.
Bevor wir jedoch auf algorithmische Details zum Addieren, Subtrahieren und Multiplizieren eingehen, wollen wir eine Datenstruktur finden. Eine einfache Möglichkeit besteht natürlich darin, Dinge in einem std :: vector zu speichern.
template< class BaseType > class BigInt { typedef typename BaseType BT; protected: std::vector< BaseType > value_; };
Möglicherweise möchten Sie überlegen, ob Sie den Vektor mit einer festen Größe erstellen und vorab zuordnen möchten. Der Grund dafür ist, dass Sie für verschiedene Operationen jedes Element des Vektors - O (n) - durchlaufen müssen. Vielleicht möchten Sie sofort wissen, wie komplex eine Operation sein wird, und ein festes n macht genau das.
Nun aber zu einigen Algorithmen zur Bearbeitung der Zahlen. Sie könnten es auf logischer Ebene tun, aber wir werden diese magische CPU-Leistung verwenden, um die Ergebnisse zu berechnen. Was wir jedoch von der logischen Darstellung von Half- und FullAdders übernehmen werden, ist die Art und Weise, wie mit Carry umgegangen wird. Überlegen Sie als Beispiel, wie Sie den Operator + = implementieren würden . Für jede Zahl in BigInt <> :: value_ würden Sie diese hinzufügen und prüfen, ob das Ergebnis eine Form von Übertrag erzeugt. Wir werden es nicht bitweise tun, sondern uns auf die Art unseres BaseType verlassen (sei es lang oder int oder kurz oder was auch immer): Es läuft über.
Wenn Sie zwei Zahlen hinzufügen, muss das Ergebnis sicherlich größer sein als die größere dieser Zahlen, oder? Ist dies nicht der Fall, ist das Ergebnis übergelaufen.
template< class BaseType > BigInt< BaseType >& BigInt< BaseType >::operator += (BigInt< BaseType > const& operand) { BT count, carry = 0; for (count = 0; count < std::max(value_.size(), operand.value_.size(); count++) { BT op0 = count < value_.size() ? value_.at(count) : 0, op1 = count < operand.value_.size() ? operand.value_.at(count) : 0; BT digits_result = op0 + op1 + carry; if (digits_result-carry < std::max(op0, op1) { BT carry_old = carry; carry = digits_result; digits_result = (op0 + op1 + carry) >> sizeof(BT)*8; // NOTE [1] } else carry = 0; } return *this; } // NOTE 1: I did not test this code. And I am not sure if this will work; if it does // not, then you must restrict BaseType to be the second biggest type // available, i.e. a 32-bit int when you have a 64-bit long. Then use // a temporary or a cast to the mightier type and retrieve the upper bits. // Or you do it bitwise. ;-)
Die andere arithmetische Operation verläuft analog. Heck, Sie könnten sogar die stl-Funktoren std :: plus und std :: minus, std :: times und std :: divides verwenden, ..., aber achten Sie auf den Übertrag. :) Sie können Multiplikation und Division auch mithilfe Ihrer Plus- und Minusoperatoren implementieren. Dies ist jedoch sehr langsam, da dadurch die Ergebnisse berechnet werden, die Sie bereits in früheren Aufrufen von Plus und Minus in jeder Iteration berechnet haben. Es gibt viele gute Algorithmen für diese einfache Aufgabe, verwenden Sie Wikipedia oder das Web.
Und natürlich sollten Sie Standardoperatoren implementieren wie
operator<<
(verschieben Sie einfach jeden Wert in value_ für n Bits nach links, beginnend mitvalue_.size()-1
... oh und denkenoperator<
Sie an den Übertrag :), - Sie können hier sogar ein wenig optimieren, indem Sie das überprüfen grobe Anzahl von Ziffern mitsize()
zuerst. Und so weiter. Dann machen Sie Ihre Klasse nützlich, indem Sie mit std :: ostream befreundet sindoperator<<
.Hoffe dieser Ansatz ist hilfreich!
quelle
Es gibt einen vollständigen Abschnitt dazu: [Die Kunst der Computerprogrammierung, Band 2: Seminumerische Algorithmen, Abschnitt 4.3 Multiple Precision Arithmetic, S. 265-318 (Hrsg. 3)]. Weitere interessante Materialien finden Sie in Kapitel 4, Arithmetik.
Wenn Sie sich wirklich keine andere Implementierung ansehen möchten, haben Sie sich überlegt, was Sie lernen möchten? Es müssen unzählige Fehler gemacht werden, und diese aufzudecken ist lehrreich und auch gefährlich. Es ist auch schwierig, wichtige Rechenökonomien zu identifizieren und über geeignete Speicherstrukturen zu verfügen, um schwerwiegende Leistungsprobleme zu vermeiden.
Eine Herausforderung für Sie: Wie wollen Sie Ihre Implementierung testen und wie möchten Sie nachweisen, dass die Arithmetik korrekt ist?
Möglicherweise möchten Sie, dass eine andere Implementierung getestet wird (ohne zu prüfen, wie sie funktioniert), aber es wird mehr als das erfordern, um verallgemeinern zu können, ohne ein exkrutierendes Testniveau zu erwarten. Vergessen Sie nicht, Fehlermodi zu berücksichtigen (Probleme mit zu wenig Speicher, zu wenig Stapel, zu lange Laufzeit usw.).
Habe Spaß!
quelle
Das Hinzufügen müsste wahrscheinlich im standardmäßigen linearen Zeitalgorithmus erfolgen,
aber für die Multiplikation könnten Sie http://en.wikipedia.org/wiki/Karatsuba_algorithm ausprobieren
quelle
Sobald Sie die Ziffern der Zahl in einem Array haben, können Sie die Addition und Multiplikation genau so durchführen, wie Sie es mit Langschrift tun würden.
quelle
Vergessen Sie nicht, dass Sie sich nicht auf 0-9 als Ziffern beschränken müssen, dh Bytes als Ziffern (0-255) verwenden müssen, und Sie können trotzdem lange Handrechnungen durchführen, wie Sie es für Dezimalstellen tun würden. Sie könnten sogar ein Array von langen verwenden.
quelle
unsigned
, was schnelles E / A und eine einfache Multiplikation ist, mit dem Nachteil, dass 59% des Speicherplatzes verschwendet werden. Ich empfehle die Basis (2 ^ 32) für fortgeschrittenes Lernen, die für alles außer E / A viel schneller als die Basis 10/10000 ist, aber die Multiplikation / Division viel schwieriger zu implementieren ist.Ich bin nicht davon überzeugt, dass die Verwendung eines Strings der richtige Weg ist - obwohl ich selbst noch nie Code geschrieben habe, denke ich, dass die Verwendung eines Arrays eines numerischen Basistyps eine bessere Lösung sein könnte. Die Idee ist, dass Sie einfach das erweitern, was Sie bereits haben, genauso wie die CPU ein einzelnes Bit in eine Ganzzahl erweitert.
Zum Beispiel, wenn Sie eine Struktur haben
typedef struct { int high, low; } BiggerInt;
Sie können dann manuell native Vorgänge für jede der "Ziffern" (in diesem Fall hoch und niedrig) ausführen, wobei Sie die Überlaufbedingungen berücksichtigen:
BiggerInt add( const BiggerInt *lhs, const BiggerInt *rhs ) { BiggerInt ret; /* Ideally, you'd want a better way to check for overflow conditions */ if ( rhs->high < INT_MAX - lhs->high ) { /* With a variable-length (a real) BigInt, you'd allocate some more room here */ } ret.high = lhs->high + rhs->high; if ( rhs->low < INT_MAX - lhs->low ) { /* No overflow */ ret.low = lhs->low + rhs->low; } else { /* Overflow */ ret.high += 1; ret.low = lhs->low - ( INT_MAX - rhs->low ); /* Right? */ } return ret; }
Es ist ein vereinfachtes Beispiel, aber es sollte ziemlich offensichtlich sein, wie man es auf eine Struktur erweitert, die eine variable Anzahl der von Ihnen verwendeten numerischen Basisklasse aufweist.
quelle
Verwenden Sie die Algorithmen, die Sie in der 1. bis 4. Klasse gelernt haben.
Beginnen Sie mit der Einsen-Spalte, dann den Zehner-Spalten und so weiter.
quelle
Wie andere sagten, machen Sie es auf altmodische Weise, aber halten Sie sich davon fern, dies alles in Basis 10 zu tun. Ich würde vorschlagen, alles in Basis 65536 zu tun und Dinge in einer Reihe von Longs zu speichern.
quelle
Wenn Ihre Zielarchitektur die BCD-Darstellung von Zahlen (Binary Coded Decimal) unterstützt, können Sie Hardware-Unterstützung für die Multiplikation / Addition mit langer Hand erhalten, die Sie durchführen müssen. Wenn Sie den Compiler dazu bringen, BCD-Anweisungen auszugeben, müssen Sie sich darüber informieren ...
Die Chips der Motorola 68K-Serie hatten dies. Nicht dass ich bitter wäre oder so.
quelle
Mein Anfang wäre, ein Array von Ganzzahlen beliebiger Größe zu haben, wobei 31 Bit und die 32n als Überlauf verwendet werden.
Die Starteroperation wäre ADD und dann MAKE-NEGATIVE unter Verwendung des 2er-Komplements. Danach fließt die Subtraktion trivial und sobald Sie add / sub haben, ist alles andere machbar.
Es gibt wahrscheinlich ausgefeiltere Ansätze. Dies wäre jedoch der naive Ansatz der digitalen Logik.
quelle
Könnte versuchen, so etwas zu implementieren:
http://www.docjar.org/html/api/java/math/BigInteger.java.html
Sie würden nur 4 Bits für eine einzelne Ziffer 0 - 9 benötigen
Ein Int-Wert würde also jeweils bis zu 8 Stellen zulassen. Ich habe beschlossen, mich an eine Reihe von Zeichen zu halten, damit ich doppelt so viel Speicher verwende, aber für mich wird es nur einmal verwendet.
Auch wenn alle Ziffern in einem einzigen Int gespeichert werden, wird dies zu kompliziert, und wenn überhaupt, kann es sogar zu einer Verlangsamung führen.
Ich habe keine Geschwindigkeitstests, aber wenn ich mir die Java-Version von BigInteger anschaue, scheint es, als würde sie sehr viel Arbeit leisten.
Für mich mache ich das unten
//Number = 100,000.00, Number Digits = 32, Decimal Digits = 2. BigDecimal *decimal = new BigDecimal("100000.00", 32, 2); decimal += "1000.99"; cout << decimal->GetValue(0x1 | 0x2) << endl; //Format and show decimals. //Prints: 101,000.99
quelle
Subtrahieren Sie 48 von Ihrer Ganzzahl und drucken Sie, um die Anzahl der großen Ziffern zu erhalten. Führen Sie dann die grundlegende mathematische Operation aus. Ansonsten werde ich eine Komplettlösung anbieten.
quelle