Umwandlung von Zahlen in Strings (GFA-Basic)

Mancher, der mit dem ATARI ST zu tun hat, wird vielleicht selten vor dem Problem stehen, eine Zahl als String in einem wissenschaftlich-technischen Format darstellen zu müssen. Mir als Student in einem wissenschaftlich-technischen Studiengang stellte es sich regelmässig, wenn es an die Auswertung irgendwelcher Laborübungen ging. Diese stupide Arbeit durfte (und darf) ‘Sarotti Computer’ erledigen - per Programm.

Mit den Programmen steht es genauso wie mit dem Häusle: Ständig ist irgendwas zu ändern, zu reparieren, besser zu machen. Nicht nur eigene Programme werden ständig verbessert, auch die Programmiersprachen werden immer besser, komfortabler und leistungsfähiger. So auch das neue GFA BASIC 3.xx. Dort wurde unter anderem die Funktion STR$() um ganze drei optionale (!!) Parameter erweitert.

Für die normale Ausgabe 123.125 ist “ 123.13” völlig ausreichend. (Schon dafür ist man sehr dankbar, weil äußerst selten.) Leider fehlten aber die Exponential-Schreibweisen, wie sie immer häufiger anzutreffen sind (für das Beispiel 123.125):

1.23125E+002 (technische Exponentialschreibweise),
0.123125E+003 (wissenschaftliche Schreibweise, PASCAL läßt grüßen).

Mein Ziel war, eine Routine zu entwickeln, die es erlaubt, daß sich in den Exponential-Schreibweisen das Zahlenformat m.dd...E±eee außer dem Vorzeichen der Mantisse m.dd... nicht ändern soll, was bei normaler Schreibweise leider der Fall ist.

Deshalb wurde ein konstant dreistelliger Exponent gewählt, der im Bereich 000 bis ±308 liegen kann.

Grundfunktion in dieser Routine ist, wie sollte es auch anders sein, die Funktion STR$(x,y,z)1. Die minimale Länge des Zahl-Strings setzt sich zusammen aus der Anzahl der Vorkomma-Stellen (VK) plus der Anzahl der Nachkommastellen (NK) plus einer Stelle für den Dezimalpunkt plus einer für das Vorzeichen (normale Schreibweise):

len = VK+NK+2 (1).

Bei den Exponentialschreib-weisen ist die Bestimmung der Stringlänge etwas einfacher. Hier haben wir ein Vorzeichen, eine Vorkommastelle, einen Dezimalpunkt, die Anzahl der Nachkommastellen, ein ‘E’, das Exponentenvorzeichen und den dreistelligen Exponenten. Es gilt:

len = 3+NK+5 = NK+8 (2).

Oftmals ist die Angabe eines Formates mit einer üblen Kopfrechnerei verbunden, weil alle mir bekannten Programmiersprachen die gesamte Anzahl an Stellen wissen wollen und die Anzahl der Nachkommastellen, wobei der Dezimalpunkt als eine Stelle gezählt wird. Und weil Programmierer eben recht faul sind, was das Kopfrechnen angeht, kann bei zwei anzugebenden Parametern auch gleich die Anzahl der Vorkomma- und Nachkommastellen übergeben werden. Dann geben wir auch gleich noch an, welches Format wir haben wollen.

Da die Routine als Hilfe zur Dateneingabe innerhalb eines Dialogs fungieren soll, geben wir auch gleich an, wie lang der String am Ende sein sollte. Die Routine ist so ausgelegt, daß eine zu kleine String-Länge schlichtweg ignoriert wird. Eine zu große wird mit Leerzeichen am Anfang aufgefüllt. Doch wie gehen wir nun vor?

Zunächst wird die übergebene Zahl ‘data’ in die lokale Variable ‘d’ kopiert, schließlich sollen irgendwelche Schwierigkeiten dadurch, daß ‘data’ kein VAR-Parameter ist, ausgeschlossen werden, außerdem zeugt es meiner Meinung nach von einem ‘sauberen’ Programmierstil.

Nehmen wir den einfachsten Fall, das Ausgabeformat Null. Dabei wird mit der übergebenen Zahl nichts weiter angestellt, Sondern nach der Gleichung (1) ein Zahl-String aufgebaut. Bei positiven Zahlen steht an erster Stelle ein Leer-und bei negativen ein Minuszeichen. Wem das nicht gefällt, der kann auch das Pluszeichen ausgeben mit:

MID$(d$,1,1)=”+”,

wobei die Wahl der nötigen Abfrage dem geneigten Leser selbst überlassen sei. Damit ist dieser Fall zunächst erledigt. Nun wenden wir uns der technischen Exponential-Schreibweise zu. Sie findet in Taschenrechnern ihre Anwendung und ist meiner Meinung nach für den Hausgebrauch die beste. Ein Manko hat sie aber doch, wie wir noch sehen werden.

Zunächst stellen wir fest, wie groß die Zahl überhaupt ist. Das geschieht mit der dekadischen Logarithmus-Funktion. Daß hierbei der Absolutwert benutzt wird, dürfte verständlich sein. Wenn |d| kleiner eins ist, erhalten wir in lg& eine Zahl, deren Betrag um eins zu groß ist, deshalb muß lg& um eins erniedrigt werden. Dann wird die Mantisse berechnet, indem man ‘d’ durch 10lg& dividiert. Da der STR$()-Befehl seinerseits eine Rundung vornimmt, kann es passieren, daß statt 9.9995001 plötzlich ’ 10.000' ausgegeben wird. Das verhindert man, indem man eine entsprechende Abfrage einbaut, bei der dieser Fall geprüft wird. Sehen wir uns diese Abfrage etwas genauer an:

IF INT(d*10^dez|+0.5 +10^(-dez|+3)))/10^dez|

Der Term 10^dez| bzw. INT(d*10^dez|)/10^dez| schneidet bis auf 3 Nachkommastellen (dez|=3) alle Dezimalen ab. Addiert man in der INT()-Anweisung dann noch die Zahl 0.5, wird die letzte Stelle gerundet. Bei Verwendung des STR$()-Befehls reichte diese einfache Rundung nicht mehr aus, irgendwie verarbeiten beide Anweisungen die gleiche Zahl anders, so daß trotz dieser Abfrage die Ausgabe der Routine ’10.000E+000’ erfolgte, statt der korrekten Ausgabe ‘1.000E+001'. Deshalb fügen wir noch eine unauffällige kleine Zahl zu den 0.5 hinzu, damit die Ausgabe korrekt erfolgt. C’est la vie...

Danach wird der Mantissen-String aufgebaut, dann derEx-ponenten-String, bei dem auch gleich auf Länge und Vorzeichen geachtet wird. Schließlich fügt man alles in der Variablen d$ zusammen.

Bei der Ausgabe im wissenschaftlichen Exponentialformat ist die Vorgehensweise entsprechend. Nur daß bei positivem Exponenten die in lg& erhaltene Zahl um eins zu klein ist und deshalb lg& inkrementiert werden muß. Auch die Abfrage für den Mantissen-String sieht etwas anders aus, ist deren Betrag doch immer kleiner eins. Damit sind wir fast am Ende. Vor Rücklieferung des Strings füllt man ihn gegebenenfalls mit Leerzeichen auf. Zu lange Strings werden nicht gekürzt, da das die Routine unnötig verlangsamt hätte.

Eingangs erwähnte ich einen Nachteil der technischen Exponentialschreibweise. Er liegt im Zahlenbereich. Während die Routine im wissenschaftlichen Exponentialformat Zahlen mit Beträgen kleiner 10-307 darstellen kann, ist bei der technischen Exponentialschreibweise bei diesem Wert das Ende der Fahnenstange erreicht. Das liegt daran, daß zur Darstellung von 9.5E-308 die Zahl durch 1.0E -308 dividiert werden muß, was im GFA-BASIC gleich Null (Unterlauf) ist. Deshalb liefert die Routine für Zahlen kleiner 1.0E-307 immer Null.

Wird die wissenschaftliche Exponentialschreibweise gewählt, kann man Zahlen kleiner 1.0E-307 darstellen, da die Mantisse bereits durch 10 dividiert worden, der Exponent also um eins größer ist. 1.0E -307 ergibt also 0.1E-306. Dagegen gibt 1.0 den Ausdruck 0.1E+001, wodurch wir sofort sehen, daß 1.0E+308 als 0.1E+309 ausgegeben wird, die Mantisse also durch 1.0E+309 dividiert wird, was einen Überlauf bewirkt, der bei der technischen Schreibweise nicht auftaucht. Der Spielraum zwischen Ober- und Untergrenze der ausgebbaren Zahlen beträgt also bei beiden Formaten 615 Zehnerpotenzen. Nur die Bereiche sind unterschiedlich. Für diejenigen, die es beim Listing vielleicht übersehen haben sollten: Diese Routine ist als FUNCTION ausgebildet, so daß der zurückgelieferte String sofort zugewiesen werden kann.

' ******************************
' *                            *
' *  Eine neue str$()-Routine  *
' *                            *
' *                            *
' *  Written by T.W. Müller    *
' *  Guerickestr. 26           *
' *                            *
' *  1000 Berlin 10            *
' *                            *
' ******************************
' * Copyright by MAXON Computer*
' *       (C) 1989             *
' *                            *
' ******************************
'
' Ab i|=7 überschreitet der Zahl-
' string die Länge von 14 Zeichen.
' Trotzdem wird er nicht gekürzt 
' ausgegeben
'
PRINT @str$(123.125,10,0,5,4)
'
FOR i|=1 TO 8
    PRINT i|,@str$(999.99500005,14,1,0,i|) 
    PRINT i|,@str$(999.99500005,14,2,0,i|) 
    IF i|<8 
        PRINT 
    ENDIF 
NEXT i|
'
FUNCTION str$(data,len|,art|,ziff|,dez|) 
    LOCAL d$,lg&,d,ex$ 
    d=data 
    IF d=0
        d=1.0E-300 
    ENDIF
    SELECT art|
    CASE 0
        d$=STR$(d,ziff|+dez|+2,dez|)
    CASE 1
        lg&=LOG10(ABS(d))
        IF ABS(d)<1 
            DEC lg&
        ENDIF
        d=d/(10^lg&)
        IF INT(d*10^dez|+0.5+10^(-(dez|+3)))/10^dez|=10 
            INC lg& 
            d=d/10 
        ENDIF
        ex$=STR$(ABS(lg&)) 
        ex$=STRING$(3-LEN(ex$),"0")+ex$
        IF lg&<0
            ex$="-"+ex$
        ELSE
            ex$="+"+ex$
        ENDIF
        d$=STR$(d,dez|+3,dez|)+"E"+ex$
    CASE 2
        lg&=WORD(LOG10(ABS(d)))
        IF SGN(lg&)=1 
            INC lg& 
        ENDIF 
        d=d/10^lg&
        IF INT(d*10^dez|+0.5+10^(-(dez|+3)))/10^dez|=1 
            INC lg& 
            d=d/10 
        ENDIF
        ex$=STR$(ABS(lg&)) 
        ex$=STRING$(3-LEN(ex$),"0”)+ex$
        IF lg&<0
            ex$="-"+ex$
        ELSE
            ex$="+"+ex$
        ENDIF
        d$=STR$(d,dez|+3,dez|)+"E"+ex$ 
    ENDSELECT 
    IF LEN(d$)<len|
        d$=SPACE$(len|-LEN(d$))+d$
    ENDIF 
    RETURN d$
ENDFUNC

Thomas Müller
Aus: ST-Computer 04 / 1990, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite