Dieser Artikel ist im Heise-Verlag in der Zeitschrift "c't", Ausgabe 04/1989 erschienen.
Er ist hier aus Gründen der besseren Übersicht dreigeteilt. Der Text ist Grundlage des Artikels
"Primzahltests und Primfaktorzerlegung". Links:
Die Realisierung
Da über die verwendete Datenstruktur nun Klarheit besteht, kommen
wir zu den Unterprogrammen, die für den Umgang mit longint-Zahlen nötig sind.
Sie sind zusammen mit den nötigen Definitionen im folgenden Programm-Listing
enthalten. Dieses soll eine Art Bibliothek darstellen. Der Inhalt sollte unter
dem Namen "longcalc.c" abgespeichert werden.
Wollen wir mit longint-Zahlen rechnen, müssen wir dem Preprozessor
dies durch eine include-Anweisung mitteilen. Die Anzahl der Elemente eines
derartigen Vektors ist durch die Konstante MaxNr gegeben, diese wird bei
Benutzung durch eine define-Anweisung festgelegt. Auf diese Weise
ist es möglich, ohne Änderung der Datei "longcalc.c" beliebig
lange Zahlen zu benutzen.
/* longcalc.c */
#define intdig 10
#define HBit 0x40000000
#define RBits 0x7FFFFFFF
#define carry(x) (x<0)
#define pos(x) (x&=RBits)
typedef long longint[MaxNr];
typedef char string[intdig*MaxNr];
longint einsl={1,1},zweil={1,2},dreil={1,3},vierl={1,4};
longint zehnl={1,10},hundertl={1,100};
/* Umwandlung string -> longint */
stol(string s, longint z)
{
int i;
long w1;
longint t,w;
i=0; longl(1L,t); longl(0L,z);
while (s[i]!='\0') i++;
while (i) {
i--; w1=s[i]-'0'; longl(w1,w);
multl(w,t,w); addl(w,z,z); multl(t,zehnl,t); }
}
/* Umwandlung longint -> string */
ltos(longint z, string s)
{
int i,j;
char ch;
longint w,y;
if (eq0l(z)) { s[0]='0'; s[1]='\0'; return; }
transl(z,y); i=0;
while (gt0l(y)) {
divmodl(y,zehnl,y,w); s[i]=w[1]+'0'; i++; }
s[i]='\0'; i--; j=0;
while (i>j) {
ch=s[i]; s[i]=s[j]; s[j]=ch; i--; j++; }
}
printl(longint x) /* Ausgabe einer longint-Zahl */
{
string s;
ltos(x,s); printf("%s",s);
}
scanl(longint x) /* Eingabe einer longint-Zahl */
{
string s;
scanf("%s",s); stol(s,x);
}
longl(long x, longint y) /* Umwandlung long-longint */
{
y[0]=1;
if (x>=0) y[1]=x; else y[1]=x&RBits;
}
transl(longint x, longint y) /* y=x */
{
int i;
for (i=0;i<=x[0];i++) y[i]=x[i];
}
eql(longint x, longint y) /* x==y */
{
int i;
for (i=0;i<=x[0];i++)
if (x[i]!=y[i]) return 0;
return 1;
}
nel(longint x, longint y) /* x!=y */
{
int i;
for (i=0;i<=x[0];i++)
if (x[i]!=y[i]) return 1;
return 0;
}
gtl(longint x, longint y) /* x>y */
{
int i;
if (x[0]>y[0]) return 1;
if (x[0]<y[0]) return 0;
for (i=x[0];i>0;i--) {
if (x[i]>y[i]) return 1;
if (x[i]<y[i]) return 0; }
return 0;
}
gel(longint x, longint y) /* x>=y */
{
int i;
if (x[0]>y[0]) return 1;
if (x[0]<y[0]) return 0;
for (i=x[0];i>0;i--) {
if (x[i]>y[i]) return 1;
if (x[i]<y[i]) return 0; }
return 1;
}
ltl(longint x, longint y) /* x<y */
{
int i;
if (x[0]>y[0]) return 0;
if (x[0]<y[0]) return 1;
for (i=x[0];i>0;i--) {
if (x[i]>y[i]) return 0;
if (x[i]<y[i]) return 1; }
return 0;
}
lel(longint x, longint y) /* x<=y */
{
int i;
if (x[0]>y[0]) return 0;
if (x[0]<y[0]) return 1;
for (i=x[0];i>0;i--) {
if (x[i]>y[i]) return 0;
if (x[i]<y[i]) return 1; }
return 1;
}
eq0l(longint x) /* x==0 */
{
return !x[x[0]];
}
gt0l(longint x) /* x>0 */
{
return x[x[0]]>0;
}
oddl(longint x) /* x ungerade ? */
{
return x[1]&1;
}
addl(longint x, longint y, longint z) /* z=x+y */
{
int i,n;
if (x[0]<y[0]) {
for (i=x[0]+1;i<=y[0];i++) x[i]=0; n=y[0]; }
else {
for (i=y[0]+1;i<=x[0];i++) y[i]=0; n=x[0]; }
z[1]=x[1]+y[1]; i=1;
while (i<n) {
if (carry(z[i])) {
pos(z[i]); i++; z[i]=x[i]+y[i]+1; }
else {
i++; z[i]=x[i]+y[i]; } }
if (carry(z[n])) { pos(z[n]); n++; z[n]=1; }
z[0]=n;
}
subl(longint x, longint y, longint z) /* z=x-y */
{
int i;
for (i=y[0]+1;i<=x[0];i++) y[i]=0;
z[1]=x[1]-y[1]; i=1;
while (i<x[0]) {
if (carry(z[i])) {
pos(z[i]); i++; z[i]=x[i]-y[i]-1; }
else {
i++; z[i]=x[i]-y[i]; } }
while (!z[i] && i>1) i--;
z[0]=i;
}
incl(longint x) /* x=x+1 */
{
int i;
x[1]++; i=1;
while (carry(x[i]) && i<x[0]) { pos(x[i]); i++; x[i]++; }
if (i==x[0] && carry(x[i])) { pos(x[i]); x[0]++; x[x[0]]=1; }
}
decl(longint x) /* x=x-1 */
{
int i;
x[1]--; i=1;
while (carry(x[i]) && i<x[0]) { pos(x[i]); i++; x[i]--; }
if (!x[x[0]] && x[0]>1) x[0]--;
}
div2l(longint x) /* x=x/2 */
{
int i;
for (i=1;i<x[0];i++)
if (x[i+1]&1) x[i]=(x[i]>>1)|HBit; else x[i]>>=1;
x[x[0]]>>=1;
if (!x[x[0]]) x[0]--;
}
mult2l(longint x) /* x=x*2 */
{
int i;
x[1]<<=1; i=1;
while (i<x[0]) {
if (carry(x[i])) {
pos(x[i]); i++; x[i]=(x[i]<<1)|1; }
else {
i++; x[i]<<=1; } }
if (carry(x[i])) { pos(x[i]); i++; x[i]=1; x[0]++; }
}
multl(longint x, longint y, longint z) /* z=x*y */
{
longint a,b,r;
if (gtl(x,y)) { transl(x,a); transl(y,b); }
else { transl(x,b); transl(y,a); }
longl(0L,r);
while (gt0l(b)) {
if (oddl(b)) addl(r,a,r);
mult2l(a); div2l(b); }
transl(r,z);
}
divl(longint x, longint y, longint z) /* z=x div y */
{
longint a,b,y1,r;
longl(0L,r); transl(x,a); transl(y,b); transl(b,y1);
while (lel(b,a)) mult2l(b);
while (nel(b,y1))
{
mult2l(r); div2l(b);
if (lel(b,a)) { subl(a,b,a); incl(r); }
}
transl(r,z);
}
modl(longint x, longint y, longint z) /* z=x mod y */
{
longint a,b,y1,r;
longl(0L,r); transl(x,a); transl(y,b); transl(b,y1);
while (lel(b,a)) mult2l(b);
while (nel(b,y1))
{
mult2l(r); div2l(b);
if (lel(b,a)) { subl(a,b,a); incl(r); }
}
transl(a,z);
}
/* d=x div y ; m=x mod y */
divmodl(longint x, longint y, longint d, longint m)
{
longint a,b,y1,r;
longl(0L,r); transl(x,a); transl(y,b); transl(b,y1);
while (lel(b,a)) mult2l(b);
while (nel(b,y1))
{
mult2l(r); div2l(b);
if (lel(b,a)) { subl(a,b,a); incl(r); }
}
transl(r,d); transl(a,m);
}
powl(longint a, longint s, longint z) /* z=a^s */
{
longint x,y,h,r;
longl(1L,r); transl(a,x); transl(s,y);
while (gt0l(y))
{
if (oddl(y)) multl(r,x,r);
multl(x,x,x); div2l(y);
}
transl(r,z);
}
/* z = a*b mod n */
abmodn(longint a, longint b, longint n, longint z)
{
longint x,y,s;
if (gtl(a,b)) { transl(a,x); transl(b,y); }
else { transl(b,x); transl(a,y); }
longl(0L,s);
while (gt0l(y)) {
if (oddl(y)) {
addl(s,x,s);
if (gel(s,n)) subl(s,n,s); }
mult2l(x);
if (gel(x,n)) subl(x,n,x);
div2l(y); }
transl(s,z);
}
/* z = a^s mod n */
ahsmodn(longint a, longint s, longint n, longint z)
{
longint x,y,r;
transl(a,x); transl(s,y); longl(1L,r);
while (gt0l(y)) {
if (oddl(y)) abmodn(r,x,n,r);
abmodn(x,x,n,x); div2l(y); }
transl(r,z);
}
/* groesster gemeinsamer Teiler */
ggtl(longint n, longint m, longint f)
{
longint p,q,r;
transl(n,p); transl(m,q);
do {
modl(p,q,r); transl(q,p); transl(r,q); }
while (gt0l(r));
transl(p,f);
}
fakl(longint x, longint f) /* r=x! */
{
longint r,y;
longl(1L,y); longl(1L,r);
while(lel(y,x)) {
multl(r,y,r); incl(y); }
transl(r,f);
}
|
Und so funktioniert es
Die erste Prozedur, die für uns momentan von Interesse ist, ist
longl. Wie bei den meisten Unterprogrammen und auch schon bei
unseren "Konstanten" deutet das "l" am Ende darauf hin, daß hier
longint-Zahlen im Spiel sind. Der Zweck von longl ist die
Umwandlung einer long-Zahl in eine longint-Zahl, was recht einfach
erreicht werden kann. Soll hiermit der Wert einer Integer-
Variablen x in longint verwandelt werden, muß man zusätzlich eine
"cast"-Umwandlung benutzen. Der Aufruf sieht dann so aus:
longl((long)x,s).
Wichtig ist: Hier wie bei allen anderen Routinen werden die
Eingabewerte einer Funktion an den Anfang der Argumentliste
geschrieben, Ergebnisse stehen am Ende. Dies ist konsequent
eingehalten, auch wenn es der üblichen Operatorenschreibweise
widerspricht. So schreibt man bei der nächsten Prozedur, die
lediglich eine longint-Zahl kopiert, transl(x,s), auch wenn dies
bei Vorhandensein eines entsprechenden Operators s=x heißen müßte.
Vergleichsoperationen
Zu den nun folgenden Vergleichsoperationen ist kaum etwas zu
sagen. Die Namensgebung orientiert sich an den Vergleichen in
Fortran. Die Routinen geben jeweils 0 oder 1 zurück, können also
direkt in Vergleichsausdrücken verwendet werden. Zwei
Spezialroutinen wurden zum Vergleich mit 0 implementiert. Diese
können helfen, Zeit zu sparen. Da es keine negativen longint-
Zahlen gibt, ist gt0l identisch zu einem Test "ungleich 0".
Außerdem finden wir hier den Test "ungerade". Dieser ist durch
eine einzige Bit-Operation realisiert.
Addition und Subtraktion
Interessanter ist beispielsweise addl, womit zwei longint-Zahlen x
und y addiert werden. Das Prinzip ist einfach. Es werden immer
zwei Elemente x[i] und y[i] zusammengezählt und z[i] zugewiesen.
Ist das Resultat negativ, hat ein Überlauf stattgefunden. Wir
löschen dann das betreffende Bit und addieren 1 zum nächst höheren
Element von z. Für beide Aktionen, Erkennen des Übertrags und
Löschen des Übertrags-Bits, haben wir ganz am Anfang zwei
nützliche Makros definiert. Die Funktionsweise von carry ist wohl
klar. Zum Löschen des höchstwertigen Bits durch pos wird das
Element von z mit der Konstanten RBits durch & (and) verknüpft. In
RBits sind alle Bits gesetzt, außer dem höchstwertigen natürlich.
So weit, so gut. Leider gibt es Ausnahmefälle, die berücksichtigt
werden müssen. Diese treten zutage, wenn x und y unterschiedlich
lang sind, also unterschiedliche Konstanten im nullten Vektor-
Element stehen haben. Ist so die longint-Zahl x etwa 3 Elemente
lang, y aber 5, dürfen die Elemente 4 und 5 von x nicht verwendet
werden, da dort unbekannte Werte stehen. Hier müssen also die
Werte von y nach z übertragen werden, der Übertrag mitgerechnet.
Der andere Fall wäre genau umgekehrt, x ist länger als y. Am Ende
werden dann die Elemente von x nach z übertragen. Schließlich gibt
es auch den Fall, daß beide gleich lang sind. Zunächst muß aber
auch erkannt werden, welcher Fall vorliegt. Summa summarum also
höchst unangenehm für den Programmierer. Wir behelfen uns hier mit
dem einfachen Trick, daß wir die fehlenden Elemente der kürzeren
Zahl, also x oder y, mit 0 vollschreiben. Dann können wir nämlich
beide als gleichlang betrachten und brauchen keine weitere
Fallunterscheidung vorzunehmen.
Dies ist zwar eine elegante Lösung, kostet aber etwas Zeit, jedoch
erreichen wir auf diese Weise den am leichtesten zu lesenden
Quellcode. Zudem ist auch nicht zu erwarten, daß die Längen der zu
addierenden Zahlen ständig stark differieren. Und schließlich gibt
es ja auch noch die Spezialroutinen incl und decl. Die eigentliche
Addition führen wir wie besprochen in der while-Schleife aus. Die
einzige Abfrage betrifft hier den Test auf Übertrag. Am Anfang
wurde n als zunächst provisorische Länge von z festgesetzt. Dies
kann nun eventuell noch um 1 erhöht werden. Diese Überprüfung
steht am Ende der Prozedur.
Ähnlich wie bei addl gehen wir bei subl vor. Hier muß y die
kleinere der beiden Zahlen x und y sein, somit wird y mit 0
aufgefüllt. Erkennen des Übertrags und Löschen des Bits werden
wieder durch die bereits besprochenen Makros carry und pos
ausgeführt. In den meisten Fällen wird die Länge von z gleich der
von x sein. Sind x und y jedoch annähernd gleich groß, kann sie
noch erniedrigt werden. Dies geschieht in der letzten Schleife.
Interessant ist die Frage: Welches Resultat erhalten wir bei
Aufrufen wir addl(x,x,x) oder subl(x,a,x)? Mit anderen Worten,
können für Ausgabewerte dieser Funktionen dieselben Variablen
verwendet werden wie für die Eingabewerte? Immerhin werden bei
addl und subl die Ausgabevariablen ja schon teilweise mit Werten
belegt, wenn die Eingabevariablen noch nicht vollständig gelesen
sind. Somit könnte es zu einem Fehlverhalten kommen.
Wenn wir die beiden Routinen aber sorgfältig durchsehen, erkennen
wir, daß das hier nicht der Fall ist. Denn das Element z[i] wird
immer erst gesetzt, wenn x[i] und y[i] nicht mehr benötigt werden.
An dieser Stelle ist anzumerken, daß das bei den meisten der
anderen Routinen nicht automatisch der Fall ist. Damit aber alle
Prozeduren beliebig benutzt werden können, wurden sie so
programmiert, daß das Ergebnis erst in einer anderen Variablen
zusammengestellt wird. Erst im letzten Schritt wird es von dieser
Hilfsvariablen in die eigentliche Ergebnisvariable kopiert.
Was passiert eigentlich, wenn wir mit subl eine Zahl von einer
kleineren Zahl abziehen wollen? Jedenfalls nichts Schlimmes. Der
Rechner stürzt nicht ab, und ein Resultat gibt es auch. Nur können
wir mit dem nicht viel anfangen. Ungünstiger endet es, wenn wir
z.B. bei einer Addition den durch MaxNr definierten Rechenbereich
überschreiten. Da spielt der Rechner meist nicht mehr mit.
Nun, worauf hier angespielt werden sollte, ist: Wir haben in die
Prozeduren keinerlei Maßnahmen gegen Fehlbedienung eingebaut.
Hierbei folgen wir im Grunde voll und ganz der C-Philosophie: Der
Anwender ist sich selbst verantwortlich, er hat sich selbst um die
korrekte Anwendung der Routinen zu kümmern. Dies ist allerdings
nicht der Grund. Vielmehr sparen wir dadurch Zeit. Wollten wir
beispielsweise bei der Addition einen Überlauf vermeiden, müßten
wir in jedem Schritt abfragen, ob die obere Grenze schon erreicht
ist. Und das dauert... Daß wir derartige Fehlerabfragen nun nicht
programmieren müssen, ist ein angenehmer Nebeneffekt.
Weiter im Text: incl(x) erhöht x um 1, decl(x) erniedrigt x um 1.
Hier werden dieselben Mechanismen angewandt wie bei addl und subl,
natürlich vereinfacht sich einiges. Die einzige Addition bzw.
Subtraktion wird im Element x[1] durchgeführt, alles andere
betrifft nur einen eventuellen Übertrag. Ohne große Änderung
könnte man Routinen programmieren, die x statt um 1 um eine
beliebige long-Zahl p vergrößern. Diese müßte natürlich in der
Parameterliste übergeben werden. Statt x[1]++ müßte man dann
x[1]+=p schreiben, analog x[1]-=p statt x[1]--.
Multiplikation und Division
Für Multiplikation und Division verwenden wir die in [1] auf den
Seiten 42 und 43 angegebenen Algorithmen. Sie sind dort in Pascal-
ähnlicher Notation zu finden. Grundlegende Routinen sind jeweils
Verdoppelung und Halbierung.
Eine Division durch 2 entspricht genau einem Rechtsshift um ein
Bit. Dies wird aus der Binärdarstellung klar. Hier zeigt sich der
Vorteil, daß wir C als Programmiersprache gewählt haben. Denn hier
existieren eigens für solche Zwecke Shift-Befehle. Wir müssen also
lediglich den gesamten Vektor um ein Bit nach rechts shiften.
Überträge sind zu berücksichtigen, wenn bei einem Vektor-Element
das nullte Bit gesetzt ist. Dieses muß dann zum nächsten Element
übertragen werden, und zwar an die Stelle 30. Hierfür haben wir am
Anfang die Konstante HBit definiert, die mit dem jeweiligen
Element durch | (or) verknüpft werden muß.
Bei der Multiplikation mit 2, die in mult2l realisiert ist, ist
hingegen ein Linksshift erforderlich. Der Übertrag kann hier
wieder durch carry(x) erkannt werden. Am Ende muß noch getestet
werden, ob ein Gesamtübertrag aufgetreten ist. Dann nämlich wird
die Länge der Zahl um 1 erhöht.
Nun kommen die eigentlichen Unterprogramme zur Multiplikation und
Division. Die Algorithmen aus [1] sind direkt in C-Syntax
übersetzt, selbstverständlich mit den Routinen für longint-Zahlen.
Der Divisions-Algorithmus ist gleich dreimal implementiert, er
liefert nämlich neben dem Resultat der Division auch den Rest. So
ist die Prozedur modl zum Erhalt des Rests vorgesehen, divmodl
liefert sowohl Ergebnis als auch Rest der Division.
powl berechnet a^s. Hierzu sind nicht s-1 Multiplikationen nötig,
sondern etwas mehr als der Zweierlogarithmus von s. Dies natürlich
nur, wenn wir die Potenzierung so wie hier programmieren. Ähnlich
funktionieren abmodn und ahsmodn, die Produkt und Potenz modulo n
ermitteln. Die Berechnung des größten gemeinsamen Teilers, ggtl,
benutzt den Euklidischen Algorithmus. Dieser ist hinlänglich
bekannt. Bleibt nur noch die Berechnung der Fakultät von x. Hier
werden einfach die erforderlichen Zahlen 1 bis x miteinander
multipliziert.
Ein- und Ausgabe
Wir holen nun das nach, was wir zu Beginn übersprungen haben,
nämlich die Routinen für Ein- und Ausgabe. Da haben wir zunächst
zwei Prozeduren zur Umwandlung eines Strings in eine longint-Zahl
und umgekehrt. Der String muß die Dezimaldarstellung einer Zahl
beinhalten, darf also nur aus Ziffern bestehen. Der Typ string ist
eigens zu diesem Zweck definiert worden. Daß dies ein Vektor vom
Typ char ist, ist klar. Fraglich ist lediglich die Länge, die hier
anzusetzen ist.
Dazu müssen wir wissen, wieviele Dezimalstellen ein Element einer
longint-Zahl liefert. Hierzu berechnet man den Zehnerlogarithmus
der größten darstellbaren long-zahl, also von 2^31-1=2147483647.
Der Logarithmus davon ist 9.33, wir runden dies auf 10, was als
Konstante intdig vereinbart wird. Ein String besteht demnach aus
intdig*MaxNr Zeichen. Diese Zahl ist normalerweise etwas zu groß,
mit dieser Tatsache muß der Anwender aber fertig werden.
Nun zu den Umwandlungsprozeduren. In stol (Umwandlung string nach
longint) holen wir uns in jedem Schritt ein Element des Strings.
Von diesem s[i] ziehen wir den Wert des Zeichens '0' ab, um den
Zahlwert des Zeichens zu erhalten. Dieser wird nach Umwandlung in
longint mit der Zehnerpotenz, entsprechend dem Stellenwert,
multipliziert. Das Resultat wird zu der Zahl z hinzuaddiert, die
schließlich das Endergebnis enthält. Die jeweils benötigte
Zehnerpotenz ist in t gespeichert. Sie muß nicht in jedem Schritt
von Grund auf neu berechnet werden, sondern aus dem alten Wert
durch Multiplikation mit 10. Das Ende eines Strings wird durch ein
Nullbyte gekennzeichnet, geschrieben '\0'. Die Abfrage hiernach
dient uns als Abbruchbedingung.
Der umgekehrte Weg wird in ltos beschritten. Eine longint-Zahl ist
gegeben. Gesucht ist der String, der die Dezimaldarstellung dieser
Zahl enthält. Hier wenden wir in jedem Schritt die Prozedur
divmodl an, die uns Ergebnis und Rest der Division durch 10
liefert. Die niederwertigste Ziffer entspricht diesem Rest. Wir
erhalten sie als longint-Zahl. Wir wissen aber sicher, daß sich
ihr Wert zwischen 0 und 9 bewegt. Deshalb können wir sie einfach
als w[1] ansprechen, womit wir auf die übliche Weise weiterrechnen
können. Addition von '0' ergibt den Zeichencode, der direkt an die
richtige Stelle des Strings s geschrieben wird.
Das Ergebnis unserer Division durch 10 muß auf dieselbe Weise
weiterbehandelt werden, deshalb haben wir hierfür auch dieselbe
Variable y eingesetzt. Dieser Vorgang geht so lange, wie y noch
ungleich 0 ist. Ans Ende wird schließlich das Nullbyte '\0'
geschrieben. Wir müssen uns nun klar darüber werden, daß wir
unsere Dezimaldarstellung genau umgekehrt in den String s
gespeichert haben. Anders geht es aber auch nicht, da wir von
vorneherein nicht wissen, wieviele Dezimalziffern die Zahl hat.
Das ist aber nicht weiter tragisch: In der letzten Schleife drehen
wir die Richtung einfach um und haben so das korrekte Resultat.
Ist z=0, müssen wir dies als Sonderfall handhaben, da die while-
Schleife überhaupt nicht durchlaufen würde. Deshalb fragen wir
dies bereits zu Beginn ab. Falls der Test erfüllt ist, liefern wir
den String "0" zurück und sind aller Sorgen ledig.
Wenn auch die Prozedur ltos wohl nur selten direkt Verwendung
finden wird, haben wir mit stol ein einfaches Mittel in der Hand,
longint-Zahlen mit Werten zu belegen, die über die Größe einer
long-Zahl hinausgehen. So weist stol("12345678912345",s) der
longint-Variablen s den Wert 12345678912345 zu. Für kleine Zahlen
sollte man dennoch longl benutzen, da dies schneller geht.
Für Ein- und Ausgabe sind die Routinen printl und scanl gedacht.
Sie sind in der Namensgebung an die Standardroutinen printf und
scanf angepaßt, funktionieren aber ohne Formatstring. Die
Realisierung ist sehr einfach, da die Hauptprogrammierarbeit in
ltos und stol steckt. Bei diesen Routinen zeigt sich der einzige
Nachteil der binären gegenüber der dezimalen Darstellung unserer
longint-Zahlen. Sie sind nämlich recht langsam. Im Vergleich zu
allen anderen Operationen kommen sie jedoch so selten vor, daß der
Nachteil nicht sehr groß ist.