Hornerschema

Betrachtet wird hier die Anwendung zur Konvertierung von Zahlensystemen und zur menschenlesbaren Zahlendarstellung.

Grundlagen

Die Zahlenkonvertierung in Zeichen ist eigentlich nicht schwer:

Dummerweise entstehen dabei die Ziffern von rechts nach links. Das mag für rechtsbündige Ausgabe in einen Speicherbereich ganz zweckmäßig sein.

Zum Vertauschen der Reihenfolge wird gern der Stack verwendet, so wie auch im ATmega-Beispiel (SendNumber) angegeben. avr-libc verwendet dazu die Funktion strrev().

Vorzeichen - Glückssache?

Die Ausgabe negativer Zweierkomplementzahlen erfordert die Negation der Zahl. Dummerweise existiert bei keinem bekannten Mikrocontroller ein NEG-mit-Carry-Befehl, deshalb lässt sich NEG nicht kaskadieren, und man muss „zu Fuß“ von Null subtrahieren. Deshalb das Makro negc — vielleicht geht's auch eleganter:

.macro negc	;Register
	ldi	r16,0
	sbc	r16,@0
	mov	@0,r16
.endm

Feldbreite, führende Nullen

Führende Leerzeichen sorgen für rechtsbündige Ausgabe, und löschen ggf. Zeichenmüll einer zuvor ausgegebenen Zahl.

Führende Nullen werden für Uhrzeit-Ausgaben sowie für Hexadezimalzahlen gern verwendet.

Festkomma-Ausgabe

C-Programmierer verwenden oftmals Gleitkommazahlen nur für die Ausgabe mit printf(). Make Program Look Big.

Clevere C-Programmierer geben Festkommazahlen schon so aus:

 printf("%d,%u", Zahl/10, Zahl%10);
 printf("%d,%02u", Zahl/100, Zahl%100);	//usw.
Das Punkt-Komma-Problem wird gleich mit erschlagen.
Aber Vorsicht! Was passiert bei negativen Zahlen? In der Regel ist Handarbeit angesagt.

Diese Vorgehensweise erspart das Einbinden einer Gleitkomma-Bibliothek, wobei die printf-Unterstützung einen großen Teil ausmacht.

Division mit Rest - das macht die Ausgaberoutine sowieso. Deshalb ist es besser, die Komma-Ausgabe nach oder in die Ausgabe einzubauen.

ATmega-Implementation

Weil man's doch öfter braucht, habe ich einige Include-Dateien zusammengestellt, mit denen man die typischen Problemchen per Rückgriff lösen kann.

Um das Rad nicht jedesmal neu erfinden zu müssen und trotz Low-Level (Assembler) voran zu kommen.

Die Ausgaberoutine SendNumber hat folgende Features:

Einfach

.include "itoa.i90"
und anschließend
	;R2..R5, R18, R19 laden
	rcall	SendNumberDez
aufrufen. (Na gut, putchar muss noch irgendwo aufrufbar sein.)

8051-Implementation

Hier kann man doch noch den div ab-Befehl verwenden. Außerdem kommt der selten verwendete Befehl xchd sinnvoll zum Einsatz.

;DWORD (4 Byte) durch 10 teilen: in 8 Nibbles!
;PE: R0 zeigt auf die Zahl in Big-Endian-Anordnung im iRAM
;PA: A = Rest (0..9)
;    R0 zeigt hinter das Divisionsergebnis
;    Zahl durch 10 dividiert
;VR: A,B=A,R0+=4,R2=0
ldiv10:	mov	r2,#8		;Anzahl Nibbles
	clr	a
divl:	swap	a		;Rest (0..9) in High-Nibble
	mov	b,a
	mov	a,@r0		;hole nächstes Nibble
	swap	a
	mov	@r0,a		;anderes Nibble retten bzw. richtig positionieren
	anl	a,#0Fh
	orl	a,b		;vorhergehenden Rest einsetzen
	mov	b,#10
	div	ab
	xchd	a,@r0		;Ergebnis-Nibble abspeichern
	mov	a,r2
	jnb	ACC.0,div1
	inc	r0		;nächstes Byte
div1:	mov	a,b		;Rest (0..9) liefern oder in nächste Runde
	djnz	r2,divl
	ret

;DWORD (4 Byte) auf Null testen
;PE: R0 zeigt hinter das letzte Byte im iRAM
;PA: A = 0 wenn alle Bytes Null sind
;    R0 zeigt auf das erste Byte
;VR: A,R0+=4,R2=0
lck0:	mov	r2,#4
	clr	a
ck0l:	dec	r0
	orl	a,@r0
	djnz	r2,ck0l
ckFe:	ret

;Dezimal-Ausgabe; zerstört die auszugebende Zahl!
;PE: R0 zeigt auf die vzl. 32-bit-Zahl in Big-Endian-Anordnung im iRAM
;VR: A,B,R2=0,R3=0
SendNumberDez:
	mov	r3,#0		;Ziffernzähler
snl:	call	ldiv10
	push	ACC
	 inc	r3
	 call	lck0		;Ergebnis Null?
	 jnz	snl		;nein, nächste Ziffer generieren
snn:	pop	ACC
	add	a,#'0'
	call	putchar		;Ziffern in umgekehrter Reihenfolge ausgeben
	djnz	r3,snn
	ret	

Der Divisionsalgorithmus mit den Nibbles funktioniert für Divisoren (= Zahlenbasis) von 2 bis 16.