Das Betriebssystem des ST bietet einiges, um Programmierern bei der Fehlersuche zu helfen. Die auffälligste Hilfe kennt jeder: Die vielgefürchteten Bomben auf dem Bildschirm informieren den Programmierer, welche Art von Fehler aufgetreten ist. Die Anzahl der Bomben entspricht dabei der Exceptionnummer. Exceptions (zu Deutsch Ausnahmen) treten auf, wenn der Prozessor auf Anweisungen trifft, die einen Bus- (Exception 2), Adressierungs- (Exception 3), Illegal-Fehler (Exception 4) usw. verursachen. Werden die Ausnahme-Vektoren für den CHK- (Exception 6) und TRAPV-Prozessorbefehl (Exception 7) nicht von Ihrem Programm genutzt, so erzeugen auch sie Bomben. Das gleiche gilt für den Trace- (Exception 9) und Privilegs-Verletzungs-Interrupt (Exception 8) und nichtinitialisierte TRAP-Interrupts. Zwei Bomben weisen somit auf einen Busfehler hin, drei Bomben auf einen Adressfehler usw.
Adresse | Belegung |
---|---|
$380.L | enthält $12345678, wenn die folgenden Daten gültig sind, d.h. wenn wirklich ein Bus- bzw. Adress-Fehler auftrat. |
$3844.L | - $3a3 enthält den Inhalt der Datenregister D0-D7 in dieser Reihenfolge. |
$3a4.L | - $3bf enthält den Inhalt der Adressregister A0-A6. |
$3c0.L | enthält den Supervisor-Stack (SSP). |
$3c4.L | enthält den Inhalt Exceptionvektors |
$3c8.L | enthält den User-Stack (USP). |
$3cc.W | bis $3eb enthält 16 Wort vom Stack. |
Tabelle 1. Die Belegung des Debug-Bereichs $380
Oft reicht diese »bombige« Information allerdings nicht aus. Im Falle eines Bus- oder eines Adress-Fehlers speichert das Betriebssystem deswegen detailliertere Informationen ab. Diese finden Sie ab Speicheradresse $380 (Tabelle 1). Das Langwort in $380 gibt an, ob die folgenden Daten gültig sind. Wenn ja, enthält $380 den Wert $12345678. Von $384 bis $3a3 liegt der Inhalt der Datenregister D0 bis D7 in dieser Reihenfolge. Von $3a4 bis $3bf finden Sie den Inhalt der Adressregister A0 bis A6. $3c0.L enthält den Supervisor-Stackpointer (SSP), $3c4.L die Adresse, auf die der Exceptionvektor zeigte, und $3c8.L den Inhalt des User-Stack-pointers (USP). Schließlich finden Sie im Bereich von $3cc bis $3eb die obersten 16 Worte vom Stack. Das Betriebssystem legt alle gespeicherten Informationen unmittelbar nach Auslösen der Exception ab. Damit ist gewährleistet, daß die Register noch den ursprünglichen Inhalt besitzen. Listing 1 zeigt, wie Sie den Bereich ab $380 in C und Omikron-Basic auswerten. (ba)
0 IF LPEEK($380)<>$12345678 THEN PRINT "$380-Bereich ungÅltig": END
1 FOR D%L=0 TO 7: PRINT "D";D%L;" = "; HEX$( LPEEK($384+D%L*4)): NEXT D%L
2 FOR A%L=0 TO 6: PRINT "A";A%L;" = "; HEX$( LPEEK($3A4+A%L*4)): NEXT A%L
3 PRINT "SSP = "; HEX$( LPEEK($3C0))
4 PRINT "Exceptionnummer = "; HEX$( LPEEK($3C4))
5 PRINT "USP = "; HEX$( LPEEK($3C8))
6 PRINT "Stack:";
7 FOR D%L=0 TO 7: PRINT HEX$( LPEEK($3CC+D%L*4));",";: NEXT D%L
/*--------- $380-Abfrage in C ---------*/
#include <tos.h> /* bzw. <osbind.h> */
#include <stdio.h>
struct DEBUGINFO {
unsigned long magic;
unsigned long dregs[8];
unsigned long aregs[7];
unsigned long ssp;
unsigned long excep_no;
unsigned long usp;
unsigned int stack[16];
} *dinfo;
main()
{
long ssp;
int i;
ssp = Super(0l);
dinfo = (struct DEBUGINFO *)0x380l;
if (dinfo->magic != 0x12345678)
printf("\n$380-Bereich ungÅltig");
else {
for (i = 0; i < 8; i++)
printf("\nD%d = $%08lx",i,dinfo->dregs[i]);
for (i = 0; i < 7; i++)
printf("\nA%d = $%08lx",i,dinfo->aregs[i]);
printf("\nSSP = $%08lx",dinfo->ssp);
printf("\nExceptionnummer = $%08lx",dinfo->excep_no);
printf("\nUSP = $%#08lx",dinfo->usp);
printf("\nStack: ");
for (i = 0; i < 16; i++)
printf("$%04x,",dinfo->stack[i]);
}
Super(ssp);
}
Listing 1. Die Auswertung der Debug-Informationen im $380-Bereich unter Omikron-Basic und C
Kaum zu glauben, aber der ST ist tatsächlich musikalisch. Denn er stellt Programmierern oder vielmehr Musikern eine Betriebssystemfunktion (XBIOS 32, »Dosound«) zur Verfügung, mit der Sie beliebige Melodien abspielen können. Das ganze geht im Hintergrund vor sich, so daß das Programm unmittelbar nach dem Aufruf normal weiterläuft. Dazu nutzt die Abspielroutine den 200 Hz-Timer C. Als Argument benötigt die Funktion die Adresse der Soundbefehle. Diese arbeiten mit den Registern und Fähigkeiten des ST-Soundchips. Folgende Soundbefehle stehen zur Verfügung:
Befehle $0-$f
Diese Befehle erwarten ein Argument, das in das der Befehlsnummer ($0-$f) entsprechende Register des Soundchips geladen wird. Die Bytefolge 0,5 schreibt beispielsweise den Wert 5 in das Soundregister 0.
Befehl $80
Diesem Befehl folgt ein Argument, das in ein temporäres Register geladen wird. Dieser Befehl ist nur in Zusammenarbeit mit Befehl $81 sinnvoll, der den Inhalt des temporären Registers weiterverarbeitet.
Befehl $81
Diesem Befehl müssen drei Argumente (Bytes) folgen. Das erste Argument ist die Nummer des Soundchip-Registers, in das der Inhalt des temporären Registers (siehe Befehl $80) gelangt. Das zweite Argument ist ein Zweierkomplement, das zum Inhalt des temporären Registers addiert wird. Dieser Vorgang wiederholt sich solange, bis der Inhalt des temporären Registers gleich dem dritten Argument ist.
Befehl $82-$ff
Diesen Befehlen folgt jeweils ein weiteres Argument. Lautet dieses Argument Null, so bricht die Soundverarbeitung ab. Ansonsten gibt dieses Argument an, wieviele Timerticks (2ms, 50 Hz) bis zur Verarbeitung des nächsten Soundbefehls vergehen.
Listing 2 zeigt, wie Sie in Omikron-Basic über die Do-sound-Funktion eine Melodie abspielen. (ba)
0 DATA 0,0,1,10,7,%110110,8,16,11,0,12,5,13,11,$FF,0,$FE
1 Schuss%L= MEMORY(100):A%L=0: READ I%L
2 WHILE I%L<>$FE
3 POKE Schuss%L+A%L,I%L:A%L=A%L+1: READ I%L
4 WEND
5 XBIOS (,32, HIGH(Schuss%L), LOW(Schuss%L))
Listing 2. Die Dosound-Routine unter Omikron-Basic
Für Programmentwicklungen ist es oft wichtig, auszutesten, ob das Programm mit anderen Speicherkonfigurationen zurechtkommt. Dabei sind vor allem die »speicherarmen« Konfigurationen 512 KByte und 1 MByte interessant. Das Betriebssystem stellt dazu diverse Systemvariablen zur Verfügung, die es erlauben, eine niedrigere Konfiguration softwaremäßig zu simulieren.
Die dazu wichtigste Systemvariable $42e.L (»phystop«) bestimmt das Ende des physikalischen Speichers. Bei einem 1 MByte-Rechner enthält die Variable den Wert $100000, bei einem 4 MByte-Rechner $400000. Wollen Sie nun beispielsweise einen 512 KByte-Rechner simulieren, so schreiben Sie als neue physikalische Grenze den Wert $80000.
Nun müssen Sie sicherstellen, daß das Betriebssystem den neuen Wert bei einem Reset auch akzeptiert. Denn normalerweise ermittelt es die Speicherkonfiguration über den Memory-Controller und überschreibt den Wert in »phystop« mit der neu errechneten oberen Speichergrenze. Um dies zu unterbinden, gibt es drei sogenannte magische Systemvariablen. Enthalten diese einen bestimmten Wert, so übernimmt das Betriebssystem die obere Speichergrenze aus »phystop«, ohne den Memory-Controller zu konsultieren. Dazu muß im Langwort $420 (»memvalid«) der Wert $752019f3, in $43a (»memval2«) der Wert $237698aa und in $51a (»memval3«) der Wert $5555aaaa stehen.
Haben Sie die vier Systemvariablen »phystop«, »memvalid«, »memval2« und »memval3« entsprechend gesetzt, so müssen Sie einen Warmstart durchführen. Listing 3 demonstriert die Simulation eines 512 KByte-ST in GFA-Basic und Assembler. (ba)
!---— GFA-Basic-Routine zum Simulieren von 512 KB
~GEMDOS(32,0) ! Supervisor anfordern
LPOKE &H42E,&H80000 ! $80000 = 512 KB
LPOKE &H420,&H752019F3 ! magic 1
LPOKE &H43A,&H237698AA ! magic 2
LPOKE &H51A,&H5555AAAA ! magic 3
a%=LPEEK(4) ! Resetadresse
CALL a% ! Warmstart durchführen
; -------- Die Assemblerroutine --------
sim512K:
clr.l -(sp)
move.w #$20,-(sp)
trap #1 ; enter Supervisor
move.l #$80000,$42e ; $80000 = 512 KB
move.l #$752019f3,$420 ; magic 1
move.l #$237698aa,$43a ; magic 2
move.l #$5555aaaa,$51a ; magic 3
move.l 4,a0 ; warmstart durchführen
jmp (a0)
Listing 3. Simulieren von 512 KB in GFA-Basic und Assembler
Eine der wichtigsten und interessantesten Neuerungen des STE im Vergleich zum Mega ST und den »alten« STs ist seine erweiterte Farbpalette mit 4096 statt bisher 512 Farbtönen. Auf dem STE liegt die Farbpalette mit seinen 16 Registern - für max. 16 Farben gleichzeitig - wie bisher ab der Adresse $ff8240. Der 16 Bit-Wert eines Registers läßt sich auf dem STE wie im Bild dargestellt aufschlüsseln. Jedem Farbanteil Rot, Grün und Blau entsprechen nun 4 Bit (0-15). Um farbkompatibel zu den älteren Modellen mit nur 3 Bit zu bleiben, ist die Wertigkeit der Bits 0 und 3 vertauscht. Der Registerwert %1100 für Rot stellt demnach den Rotanteil 9 (%1001) dar. (ba)
Der ST verfügt nicht nur über seine internen Systemzeichensätze. Über die »ASSIGN.SYS«-Datei bestimmen Sie, welche Zeichensätze für welche Auflösungen zusätzlich zur Verfügung stehen. Listing 4 demonstriert das ordnungsgemäße Installieren, Anzeigen und De-Installieren der verfügbaren GDOS-Fonts für SPC-Modula-2.
Das Programm geht wie folgt vor: Zunächst macht es alle im System über »ASSIGN.SYS« angemeldeten Zeichensätze mit der VDI-Funktion »load_fonts« verfügbar. Die Funktion gibt beim ersten Aufruf die Anzahl der neu geladenen Zeichensätze zurück. Nun erfragen wir die Zeichensatznummer und geben die erhaltenen Informationen auf dem Bildschirm aus. Mit der Funktion »set_font« selektieren Sie den aktuellen Zeichensatz. Dabei übergeben Sie einfach seine Zeichensatznummer. Am Ende löschen wir alle geladenen Fonts mit »unload_fonts«. (ba)
MODULE GDosFontTest;
IMPORT VDIAttributes,
VDIControls ,
VDIInquires ,
AESGraphics ,
InOut ;
VAR FontName :ARRAY [0..31] OF INTEGER;
FontNameStr :ARRAY [0..31] OF CHAR;
FontNr :INTEGER;
Search :CARDINAL;
VDIHandle :INTEGER;
i :CARDINAL;
In :VDIControls.WorkstationInitRec;
Out :VDIControls.WorkstationDescription;
PROCEDURE InitVDI;
BEGIN
WITH In DO
DeviceId := 1;
LineStyle := VDIAttributes.Solid;
LineColour := 1;
MarkerType := VDIAttributes.Dot;
MarkerColour := 1;
Font := VDIAttributes.BigFont;
TextColour := 1;
FillStyle := VDIAttributes.Filled;
FillIndex := 1;
FillColour := 1;
CoordinateSystem:= VDIAttributes.RasterCoords;
END;
VDIHandle:=AESGraphics.Handle(VDIHandle, VDIHandle,VDIHandle,VDIHandle);
VDIControls.OpenVirtualWorkstation(In,VDIHandle,Out);
INC (Out.MaxFonts,VDIControls.LoadFonts(VDIHandle, 0));
END InitVDI;
PROCEDURE TermVDI;
BEGIN
VDIControls.UnloadFonts(VDIHandle, 0);
VDIControls.CloseVirtualWorkstatior(VDIHandle);
END TermVDI;
BEGIN
InitVDI;
FOR Search:=1 TO Out.MaxFonts DO
FontNr :=VDIInquires.InquireFaceName(VDIHandle,Search,FontName);
FOR i:=0 TO 31 DO
FontNameStr[i]:=CHR(FontName[i])
END;
InOut.WriteInt(FontNr,5);
InOut.WriteString(' ');
InOut.WriteString(FontNameStr);
InOut.WriteLn;
END;
TermVDI;
END GDosFontTest.
Listing 4. GDOS-Fonts unter SPC-Modula-2 (von M. Krischik)
Für Spiele und spezielle Applikationen ist es oft notwendig, die vom Betriebssystem installierte Maus- oder Joystick-Routine durch eine eigene zu ersetzen. Über die XBIOS-Funktion 34 (»Kbdvbase«) erhalten Sie einen Zeiger auf eine Vektortabelle, über die Sie neben der Maus-Routine unter anderem auch eine eigene Joystick-Routine installieren können. Tabelle 2 zeigt den Aufbau der Vektortabelle. Der Tabelleneintrag 5 (Offset 16) enthält die Adresse der aktuellen Maus-Routine und Eintrag 7 (Offset 24) die Adresse der aktuellen Joystick-Routine. Die anderen Einträge dienen zum Abfangen von Tastatur- und MIDI-Übertragungsfehlern, sowie zum Verwalten des Keyboard-, Uhrzeit- und MIDI-Ports.
Um einen Vektor auf eine eigene Routine umzulegen, überschreiben Sie einfach den Tabelleneintrag mit der neuen Adresse. Dabei gehört es zum guten Stil, den alten Eintrag zuvor zu retten und bei Programmende wieder zurückzuschreiben. Ihre Routine erhält ab nun die entsprechenden Datenpakete. Sollten Sie die Ml-Dl-Port-Routine ersetzt haben, so erhalten Sie das übertragene Byte in DO. Alle anderen Routinen der Vektortabelle - so auch die Maus- und Joystick-Routine - erhalten einen Zeiger auf das übertragene Datenpaket in AO und dem Stack. Die Routinen werden mit »rts« abgeschlossen und sollten nicht länger als 1 ms Zeit beanspruchen. Listing 5 demonstriert die Joystick-Verwaltung in Assembler. Sie können es als Gerüst für eigene Joystick-Routinen verwenden. Brechen Sie das Programm mit Knopfdruck ab. (ba)
Offset | Bezeichnung | Bedeutung |
---|---|---|
$0 | midivec | MIDI Eingabe (Datenbyte in D0.b) |
$4 | vkbderr | Tastaturfehler |
$8 | vmierr | MIDI-Fehler |
$c | statvec | IKBD-Status |
$10 | mousevec | Maus-Verwaltung |
$14 | clockvec | Uhrzeit-Verwaltung |
$18 | joyvec | Joystick-Verwaltung |
Tabelle 2. Die KDBVBASE-Vektortablle
bsr init ; Alles vorbereiten
bsr request ; Joystick-Abfrage
bsr deinit ; Alles wieder herstellen
clr.w -(sp)
trap #1 ; chiao
init:
pea joy_on
move.w #1,-(sp)
move.w #25,-(sp)
trap #14 ; Joystick aktivieren
addq.l #8,sp
move.w #34,-(sp)
trap #14 ; Joy/mouse-Vektoren setzen
addq.l #2,sp
move.l d0,a0
lea $18(a0),a0
move.l a0,oldjoyadr ; Alte Adresse merken
move.l (a0),oldjoyvec
move.l #joyvec,(a0) ; Neue Adr. setzen
rts
deinit:
move.l oldjoyadr,a0 ; Alte Joystick-Routine
move.l oldjoyvec,(a0) ; wieder setzen
pea joy_off
move.w #1,-(sp)
move.w #25,-(sp)
trap #14 ; Joystick deaktivieren
addq.l #8,sp
rts
; Eigener Joystick-Vektor
joyvec:
cmp.b #$ff,(a0) ; Joystick 1?
bne nothing
move.b 2(a0),joystate ; Bit-kodiert
nothing:rts
; Bit 0=hoch,1=runter,2=links,3=rechts,7=Knopf (auch kombin.)
; kehrt bei Knopfdruck zurÅck
request:
move.b joystate,d7
btst #7,d7 ; Knopf gedrÅckt?
beq next1
lea strbutton,a0
bra print ; bei Knopf Schleife verlassen
next1: btst #0,d7 ; Hoch?
beq next2
lea strtop,a0
bsr print
next2: btst #1,d7 ; Runter?
beq next3
lea strdown,a0
bsr print
next3: btst #2,d7 ; Links?
beq next4
lea strleft,a0
bsr print
next4: btst #3,d7 ; Rechts?
beq next5
lea strright,a0
bsr print
next5: bra request
print:
pea (a0)
move.w #9,-(sp)
trap #1
addq.l #6,sp
rts
joy_on: dc.b $12,$14
joy_off: dc.b $15,$8
strbutton: dc.b 13,10,"Knopf gedrückt!",0
strtop: dc.b 13,10,"Hoch",0
strdown: dc.b 13,10,"Runter",0
strleft: dc.b 13,10,"Links",0
strright: dc.b 13,10,"Rechts",0
BSS
oldjoyadr: ds.l 1
oldjoyvec: ds.l 1
joystate: ds.b 1
END
Listing 5. Das Gerüst einer Joystick-Verwaltung in Assembler
Wer schon einmal versucht hat, ein Spiel - vielleicht sogar ein Ballerspiel - zu schreiben, der hat sicherlich festgestellt, daß die Sprite-Routinen des Betriebssystems nicht ideal sind. Einerseits sind sie zu langsam und andererseits erlauben sie nur Objekte der Größe 16x16 Pixel. Listing 6 stellt eine flexible Routine dar, die beliebig hohe und 16 Pixel breite Sprites auf dem Bildschirm darstellt. Dazu übergeben Sie fünf Parameter: die x- (DO.w) und y-Koordinate (Dl.w) der oberen linken Ecke des Bildschirm, an der das Objekt erscheinen soll, die Höhe des Objekts in Bildschirmzeilen (D2.w), die Bildschirmbasis (A0) und die Adresse der Objektdaten (A1). Die Objektdaten sind zeilenweise abgelegt, wobei 8 Byte (4 Bitplanes a 16 Bit) einer Objektzeile entsprechen.
Die Routine berechnet sich aus den Koordinaten und der Bildschirmbasis die Bildschirmzieladresse und den Bitoffset. Nun kopiert es das Objekt in einer Schleife zeilenweise in den Bildschirmspeicher. Dabei verwendet es den ODER-Schreibmodus. Denn somit bleibt der ursprüngliche Hintergrund bei nicht gesetzten Punkten des Objekts bestehen. (ba)
; d0=x,d1=y,d2=h,a0=bildschirmbasis,a1=spritedaten
show_16sprite:
movem.l d0-a6,-(sp)
move.w d1,d3 ; y-Koordinate
lsl.w #5,d3 ; x 32
lsl.w #7,d1 ; x 128
add.w d3,d1 ; -> x 160
move.w d0,d3 ;x-Koordinate
moveq.l #16,d4
and.w #$f,d3 ; pixels to shift
sub.w d3,d4
;
and.b #$f0,d0
lsr.w #1,d0 ;XPOS/2
add.w d1,d0 ;y+x
add.w d0,a0 ;add offset to base screen address
;
lea 16(a0),a0 ; da mit -(Ax) operiert wird
subq #1,d2 ; ZeilenzÑhler
move.w d2,d6
nextline:
moveq.l #0,d0
moveq.l #0,d1
moveq.l #0,d2
moveq.l #0,d3
move.w (a1)+,d0
move.w (a1)+,d1
move.w (a1)+,d2
move.w (a1)+,d3
lsl.l d4,d0 ;shift all bit planes
lsl.l d4,d1
lsl.l d4,d2
lsl.l d4,d3
move.l d0,d5 ; Maske bilden
or.l d1,d5
or.l d2,d5
or.l d3,d5
not.l d5
and.w d5,-(a0) ;now map data onto the screen
or.w d3,(a0)
swap d3
and.w d5,-(a0)
or.w d2,(a0)
swap d2
and.w d5,-(a0)
or.w d1,(a0)
swap d1
and.w d5,-(a0)
or.w d0,(a0)
swap d0
swap d5 ; nÑchste 16 Bit
and.w d5,-(a0)
or.w d3,(a0)
and.w d5,-(a0)
or.w d2,(a0)
and.w d5,-(a0)
or.w d1,(a0)
and.w d5,-(a0)
or.w d0,(a0)
lea 176(a0),a0 ;set up for next line
dbf d6,nextline
movem.l (sp)+,d0-a6
rts
Listing 6. Schnelle farbige Sprites in Assembler
Rotationen, perspektivische Verzerrungen, Verkleinerungen und Vergrößerungen von grafischen Objekten sind nur einige Beispiele, in denen man der Genauigkeit wegen besser mit Fließkommazahlen als mit Integerzahlen rechnet. Doch eins ist gewiß: Die Berechnungsgeschwindigkeit leidet darunter enorm. Um einerseits genau zu rechnen, aber andererseits in der Geschwindigkeit von Integeroperationen zu arbeiten, gibt es einen einfachen Trick, der vor allem in schnellen 3D-Programmen eingesetzt wird: Integerzahlen, bei denen ein Teil den Vor- und ein anderer den Nachkommateil darstellt. Listing 7 demonstriert die Verwendung von Integer-Floats anhand einfacher Rechenoperationen und Typumwandlungen in C.
Für ein durchschnittliche Genauigkeit reicht es, einen 16 Bit-Vor- und einen 16 Bit-Nachkommateil -zusammen ein Langwort (32 Bit) - zu verwenden.
Um nicht alle Float-Operationen wie etwa »arcsin« gänzlich neu zu schreiben, können Sie Integer-Floats in echte Fließkommazahlen umwandeln und anders rum. Elementare Fließkommaoperationen wie »sin« und »cos« können Sie stark beschleunigen: Sie legen für eine vernünftige Auflösung - sagen wir 1/10 Grad - die vorberechneten Integer-Floats in einer Tabelle ab. Nun können Sie Berechnungen durch Tabellenzugriffe ersetzen. (ba)
/* Long: 11111111 11111111 11111111 11111111 */
/* ----------------- ----------------- */
/* Vorkommawert Nachkommawert */
#include <stdio.h>
#include <math.h>
typedef unsigned long INTFL; /* Integerfloat */
double intfl_to_double(INTFL i) {
return (double)(i>>16)+((double)(i&0xffff)/(double)65536);
}
INTFL double_to_intfl(double f) {
double f2 = floor(f);
return ((INTFL)f2<<16)+((f-(double)(INTFL)f2)*(double)65536);
}
main()
{
INTFL i = 0x00010000; /* i = 1.0 */
for (; i < 0x000a0000; i += 0x00000010)
printf("\ni = $%lx (%lf)",i,intfl_to_double(i));
printf("\n$%lx=1.5,$%lx=2.25,$%lx=10.9",
double_to_intfl((double)1.5),
double_to_intfl((double)2.25),
double_to_intfl((double)10.9));
printf("\n%lf=$18000,%lf=$24000,%lf=$ae666",
intfl_to_double((INTFL)0x00018000),
intfl_to_double((INTFL)0x00024000),
intfl_to_double((INTFL)0x000ae666));
}
Listing 7. Mit diesem Gerüst können Sie sich in C eine eigene Integer-Float-Library aufbauen