2008-11-06 7 views
69

Ich möchte eine große int-Klasse in C++ als Programmierübung implementieren — eine Klasse, die Zahlen größer als eine lange int handhaben kann. Ich weiß, dass es bereits einige 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.Wie zu implementieren, große Int in C++

Ich verstehe, dass die allgemeine Strategie ist, die Zahl als eine Zeichenfolge zu erhalten, und dann in kleinere Zahlen aufzuteilen (z. B. einzelne Ziffern), und sie in einem Array zu platzieren. An diesem Punkt sollte es relativ einfach sein, die verschiedenen Vergleichsoperatoren zu implementieren. Mein Hauptanliegen ist, wie ich Dinge wie Addition und Multiplikation implementieren würde.

Ich bin auf der Suche nach einem allgemeinen Ansatz und Beratung im Gegensatz zu tatsächlichen Arbeitscode.

+10

Wenn dies eine Programmieraufgabe ist, starten Sie die Codierung. Sie können sonst keine Übung bekommen! –

+3

Die erste Sache - Ziffernfolgen sind in Ordnung, aber denke in Bezug auf die Basis 2^32 (4 Milliarden ungerade unterschiedliche Ziffern). Vielleicht sogar Base 2^64 in diesen Tagen. Zweitens, arbeiten Sie immer mit vorzeichenlosen Integer "Ziffern". Sie können das Zweierkomplement für signierte Big Integers selbst durchführen, aber wenn Sie versuchen, Ihre Overflow-Behandlung usw. mit vorzeichenbehafteten Ganzzahlen auszuführen, werden Sie auf Standards stoßen - undefinierte Verhaltensprobleme. – Steve314

+3

Für Algorithmen - für eine grundlegende Bibliothek sind diejenigen, die Sie in der Schule gelernt haben, ungefähr richtig. – Steve314

Antwort

35

Dinge für eine große int-Klasse zu berücksichtigen:

  1. Mathematische Operatoren: +, -, /, *,% Vergessen Sie nicht, dass Ihre Klasse auf beiden Seiten des Betreiber sein kann, dass die Betreiber werden können gekettet, dass einer der Operanden könnte ein int, float, double usw.

  2. I/O-Operatoren sein: >>, < < Dies ist wo Sie herausfinden, wie man richtig Erstellen Sie Ihre Klasse aus Benutzereingaben und wie Sie sie für die Ausgabe formatieren.

  3. Conversions/Casts: Herauszufinden welche Typen/Klassen Ihre große int Klasse Cabrio sein sollte, und , wie man richtig die Umwandlung zu behandeln. Eine schnelle Liste würde enthalten doppelte und float, und kann int enthalten (mit den richtigen Grenzen Prüfung) und komplex (unter der Annahme, kann den Bereich behandeln).

+1

Siehe [hier] (http://stackoverflow.com/questions/4421706/operator-overloading) für die idiomatischen Möglichkeiten, die Betreiber zu tun. –

+3

Bei Ganzzahlen ist der Operator << and >> Bit-Shift-Operationen. Sie als I/O zu interpretieren wäre schlechtes Design. – Dave

+1

@Dave: Außer, dass es Standard C++ ist, 'Operator <<' und 'Operator >>' mit 'Iostream's für I/O zu verwenden. – Hurkyl

5

Sobald Sie die Ziffern der Zahl in einem Array haben, können Sie Addition und Multiplikation genau so tun, wie Sie sie longhand tun würden.

0

Verwenden Sie die Algorithmen, die Sie in der ersten bis vierten Klasse gelernt haben.
Beginnen Sie mit der ersten Spalte, dann die Zehner und so weiter.

4

Vergessen Sie nicht, dass Sie sich nicht auf 0-9 als Ziffern beschränken müssen, dh verwenden Sie Bytes als Ziffern (0-255), und Sie können weiterhin lange Handarithmetik wie für Dezimalziffern verwenden . Sie könnten sogar ein Array von Long verwenden.

+0

Wenn Sie die Zahlen in Dezimalzahlen (d. H. Für Normalsterbliche) darstellen möchten, ist der 0-9 pro Nibble-Algorithmus einfacher. Gib einfach den Speicher auf. – dmckee

+0

Sie denken, dass es einfacher ist, BCD-Algorithmen als ihre regulären binären Gegenstücke zu machen? – Eclipse

+2

AFAIK base 10 wird oft verwendet, weil die Umwandlung großer Zahlen in Basis 255 (oder irgendetwas nicht 10er) von/nach Basis 10 teuer ist und die Ein- und Ausgabe Ihrer Programme im Allgemeinen in Basis 10 erfolgt. – Tobi

5

Interessanterweise hatte ich gerade ein Video darüber geschaut, bevor ich Ihre Frage sah. Here is the link. Mit freundlicher Genehmigung des Chaos Communication Congress.

+0

http://dl.fefe.de/bignum.pdf scheint wie die Folien des Videos! –

+0

Aber es glaubt, es gibt keine Erklärung über die Teilung ... –

2

Wie andere gesagt, tut es auf altmodische lange Hand Art und Weise, aber bleiben Sie weg diese Basis in alle zu tun, 10. Ich würde vorschlagen, alles in Basis 65536 und Speicher Dinge in einem Array von tun sehnt sich.

1

Wenn Ihre Zielarchitektur BCD (binär codierte Dezimalzahl) Darstellung von Zahlen unterstützt, können Sie einige Hardware bekommen Unterstützung für die Langhand Multiplikation/Addition, die Sie tun müssen. Den Compiler zu veranlassen, BCD-Anweisung zu emittieren, müssen Sie auf lesen ...

Die Motorola 68K Reihenspäne hatten dieses. Nicht dass ich verbittert oder so bin.

3

Ich bin nicht davon überzeugt, eine Zeichenfolge zu verwenden, ist der richtige Weg - obwohl ich selbst nie Code geschrieben habe, denke ich, dass die Verwendung eines Arrays eines numerischen Basis-Typs eine bessere Lösung sein könnte. Die Idee ist, dass Sie einfach erweitern, was Sie bereits auf die gleiche Weise haben, wie die CPU ein einzelnes Bit in eine ganze Zahl erweitert.

Zum Beispiel, wenn Sie eine Struktur haben,

typedef struct { 
    int high, low; 
} BiggerInt; 

können Sie dann manuell nativen Operationen auf der Grundlage der „Ziffern“ perform (hoch und niedrig, in diesem Fall), die Überlaufbedingung aufmerksam zu sein:

Es ist ein einfaches Beispiel, aber es sollte ziemlich offensichtlich sein, wie man es auf eine Struktur ausdehnt, die eine variable Nummer der von Ihnen verwendeten numerischen Basisklasse hat.

+0

Per String bedeutete das OP, eine Zeichenkette mit der gewünschten Zahl in seiner numerischen Darstellung (unter welcher Basis) zu nehmen und BigInt mit dem Wert zu initialisieren. – KTC

+0

STLPLUS verwenden Zeichenfolge, um große Ganzzahl zu halten. – lsalamon

0

Mein Start wäre ein beliebig großes Array von ganzen Zahlen zu haben, mit 31 Bits und die 32n'd als Überlauf.

Der Starter Op wäre ADD, und dann, MAKE-NEGATIVE, mit 2-Komplement. Danach fließt die Subtraktion trivial, und sobald Sie add/sub haben, ist alles andere machbar.

Es gibt wahrscheinlich anspruchsvollere Ansätze. Aber das wäre der naive Ansatz der digitalen Logik.

2

Schauen Sie sich here an, um zu sehen, wie GMP seine Algorithmen implementiert.

25

Es gibt einen vollständigen Abschnitt zu diesem Thema: [Die Kunst der Computerprogrammierung, Band 2: Seminumerische Algorithmen, Abschnitt 4.3 Multiple Precision Arithmetic, S. 265-318 (ed.3)]. Sie finden vielleicht weiteres interessantes Material in Kapitel 4, Arithmetik.

Wenn Sie wirklich nicht auf eine andere Implementierung schauen wollen, haben Sie überlegt, was Sie lernen sollen? Es gibt unzählige Fehler zu machen und diese aufzudecken ist lehrreich und auch gefährlich. Es gibt auch Herausforderungen bei der Identifizierung wichtiger rechnerischer Einsparungen und geeigneter Speicherstrukturen zur Vermeidung ernsthafter Leistungsprobleme.

Eine Herausforderung Frage an Sie: Wie beabsichtigen Sie, Ihre Implementierung zu testen, und wie schlagen Sie vor, zu zeigen, dass die Arithmetik korrekt ist?

Sie möchten vielleicht eine andere Implementierung testen (ohne zu sehen, wie es funktioniert), aber es wird mehr als das erfordern, um verallgemeinern zu können, ohne ein excrutiatives Testniveau zu erwarten. Vergessen Sie nicht, Fehlermodi zu berücksichtigen (Probleme mit zu wenig Arbeitsspeicher, zu wenig Arbeitsspeicher, zu lange Arbeitsdauer usw.).

Viel Spaß!

+1

Der Vergleich mit einer Referenzimplementierung bringt Sie nicht weiter, denn dann haben Sie ein anderes Problem: Wie testen Sie, ob die Referenzimplementierung auch korrekt ist? Das gleiche Problem ist das Testen von Wissen im Allgemeinen: Wenn ein Mann einen anderen testen muss, wer wird den ersteren testen? Es gibt keinen Ausweg aus diesem Problem, außer einem, das vor langer Zeit erfunden wurde: Beweisen von Axiomen. Wenn die Menge der Axiome als richtig angesehen wird (keine Widersprüche) und der Beweis gemäß den Regeln der Logik richtig abgeleitet wird, kann sie nicht falsch sein, selbst für unendlich viele Fälle, die niemand testen könnte. – SasQ

40

Eine lustige Herausforderung.:)

Ich nehme an, dass Sie ganze Zahlen beliebiger Länge wollen. Ich schlage folgenden Ansatz vor:

Betrachten Sie die binäre Natur des Datentyps "int". Denken Sie daran, einfache binäre Operationen zu verwenden, um zu emulieren, was die Schaltungen in Ihrer CPU tun, wenn sie Dinge hinzufügen. Wenn Sie sich intensiver dafür interessieren, lesen Sie bitte this wikipedia article on half-adders and full-adders. Sie werden etwas Ähnliches tun, aber Sie können so niedrig wie das gehen - aber faul zu sein, dachte ich, ich würde einfach verzichten und eine noch einfachere Lösung finden.

Bevor wir jedoch auf algorithmische Details zum Addieren, Subtrahieren, Multiplizieren eingehen, wollen wir eine Datenstruktur finden. Ein einfacher Weg ist natürlich, Dinge in einem std :: vector zu speichern.

template< class BaseType > 
class BigInt 
{ 
typedef typename BaseType BT; 
protected: std::vector<BaseType> value_; 
}; 

Vielleicht möchten Sie prüfen, ob Sie den Vektor mit einer festen Größe machen wollen und wenn es vorzubelegen. Grund dafür ist, dass Sie für verschiedene Operationen jedes Element des Vektors durchlaufen müssen - O (n). Sie möchten vielleicht wissen, wie komplex eine Operation sein wird und ein festes n genau das tut.

Aber jetzt zu einigen Algorithmen auf den Zahlen arbeiten. Sie könnten dies auf einer logischen Ebene tun, aber wir werden diese magische CPU-Leistung verwenden, um Ergebnisse zu berechnen. Aber was wir von der Logik-Illustration von Half- und FullAdders übernehmen werden, ist der Umgang mit Übertragungen. Betrachten Sie als Beispiel, wie Sie den + = Operator implementieren würden. Für jede Zahl in BigInt <> :: value_ würden Sie diese hinzufügen und sehen, ob das Ergebnis irgendeine Art von Übertrag erzeugt. Wir werden es nicht bitweise tun, sondern uns auf die Art unseres BaseType verlassen (sei es long oder int oder short oder was auch immer): es läuft über.

Sicher, wenn Sie zwei Zahlen hinzufügen, muss das Ergebnis größer sein als die größere dieser Zahlen, richtig? Wenn nicht, 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 anderen arithmetischen Operationen gehen analog. Verdammt, du könntest sogar die stl-functors std :: plus und std :: minus, std :: times und std :: divides, ... benutzen, aber achte auf den Übertrag. :) Sie können Multiplikation und Division auch implementieren, indem Sie Ihre Plus- und Minusoperatoren verwenden, aber das ist sehr langsam, da das die Ergebnisse berechnet, die Sie bereits in früheren Aufrufen bei jeder Iteration zu Plus und Minus berechneten. Es gibt viele gute Algorithmen für diese einfache Aufgabe, usewikipedia oder das Web.

Und natürlich sollten Sie Standard-Operatoren implementieren wie operator<< (nur jeden Wert verschieben in Wert_ nach links für n Bits, am value_.size()-1 gestartet ... ach ja, und erinnere mich an die Übertrags :), operator< - Sie können sogar Hier ein wenig optimieren und die grobe Anzahl der Ziffern zuerst mit size() überprüfen. Und so weiter. Dann machen Sie Ihre Klasse nützlich, indem Sie befriendig std :: ostream operator<<.

Hoffe dieser Ansatz ist hilfreich!

+4

"int" (wie in signiert) ist eine schlechte Idee. Undefiniertes Verhalten von Standards bei Überläufen macht es schwierig (wenn nicht unmöglich), die Logik zumindest portabel zu machen. Es ist jedoch ziemlich einfach, in Zweierkomplementen mit vorzeichenlosen Ganzzahlen zu arbeiten, wobei das Überlaufverhalten streng definiert ist, um Modulo 2^n-Ergebnisse zu ergeben. – Steve314

-1

subtrahieren Sie 48 von Ihrer Ganzzahl und drucken Sie, um die Anzahl der großen Ziffern zu erhalten. dann führen Sie die grundlegende mathematische Operation durch. ansonsten werde ich eine komplette Lösung anbieten.

0

könnten versuchen, so etwas wie diese Umsetzung:

http://www.docjar.org/html/api/java/math/BigInteger.java.html

Sie würden nur noch 4 Bits für eine einzelne Ziffer von 0 bis 9

So int-Wert bis 8 Ziffern ermöglichen würde jeder. Ich entschied, dass ich mit einer Reihe von Zeichen bleiben würde, also verwende ich den doppelten Speicher, aber für mich wird es nur einmal benutzt.

Auch wenn Sie alle Ziffern in einem einzigen int speichern, wird es übermäßig kompliziert und wenn überhaupt, kann es sogar verlangsamen.

Ich habe keine Geschwindigkeitstests, aber wenn ich mir die Java-Version von BigInteger anschaue, scheint es eine Menge Arbeit zu sein.

Für mich ich die 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 
+0

OP hat nie gesagt, dass er/sie sich auf Dezimalziffern konzentrieren möchte. – einpoklum