Beiträge von Mike im Thema „Dividieren - here we go again!“

    Da hatte ich einfach den ganzen INIT-Block zur Vereinfachung weggelassen. dvd und dvd+1 ($fb und $fc in deinem Code) müssen ja auch vorher 0 gesetzt werden, oder?

    Nochmal: ohne den expliziten "LDA #0"-Befehl ganz am Anfang ist die Routine kaputt! Wenn der Befehl fehlt und beim Eintritt in die verbockte Routine ein nicht-Null Wert im Akku steht, dann rechnet sie nicht richtig.

    Die Eingangsdaten in zp, zp+1, zp+2 sind da anders gelagert. Auch wenn in zp und zp+1 nicht 0 drin steht, wird die ganze 24-Bit-Zahl korrekt durch 7 geteilt. Darauf wollte ich auch mit dem Beispiel von "23.5/7" hinweisen - dann steht nämlich im "gebrochenen" Teil des Dividenden (also, in $FB und $FC) was anderes als Null.

    Noch zur Ergänzung/Korrektur meines Post Bitte melde dich an, um diesen Link zu sehen.:

    Der Dividend muß dann auch nicht mehr zwangweise ganzzahlig sein, [...]

    genauer: Der originale Dvidend [...]

    Ob man jetzt den Zugang über eine Fixpunkt-Darstellung oder allgemeiner, über Skalierung sucht, wesentlich ist, daß der Computer auf keinen Fall mit echt gebrochenen Werten rechnen kann *) - allenfalls mit sehr großen ganzen Zahlen! Was man da als "Fließkommazahlen" verkauft bekommt, sind tatsächlich Maschinenzahlen und die sind selbst nur eine endliche(!) Teilmenge der rationalen Zahlen. Der Exponent sorgt dann dafür, daß man nicht mit einer fixen Skalierung rechnen muß, und dadurch kann dann größenordnungsmäßig ein größerer Zahlbereich zwischen 0 und unendlich abgedeckt werden.


    *) sonst wärst Du nämlich gerade mit einem Analog-Rechner unterwegs und der ist dann in der Genauigkeit durch Rauschen limitiert. :)

    Du rechnest ja tatsächlich den Rest von 23x65536 / 7 aus, dieser Rest ist 4 und das ist auch der Rest der zur Rundung der 16. "Nachkommastelle" (in binär) relevant ist!

    Noch dazu:

    Der Dividend fängt in der Gesamtbetrachtung bei Bit 16 an, so gesehen muss sein Wert deshalb auch mit 2^16 malgenommen werden.

    Tatsächlich skalieren wir hier den Dividenden mit 2^16 und damit auch den Quotient. Der Dividend muß dann auch nicht mehr zwangweise ganzzahlig sein, Du kannst z.B. auch 23.5/7 ausrechnen, indem Du (skaliert) 1540096 / 7 rechnest (gibt 220013 mit Abrunden -> 220013/65536 = 3.35713... und 23.5/7 = 3.35714...)

    Muss ich wirklich das ganze Rundungsbyte zusammennudeln, obwohl mich nur das höchste Bit interessiert?

    Nein, das ist nicht nötig. Im Akku steht nach Ausführung der Routine der Rest und Du mußt nur nachprüfen, ob der Rest >= Divisor/2 ist. Die dazu nötige Nachbehandlung sieht wie folgt aus:

    Statt #$07 geht auch wieder ein variabler Wert, der muß dann natürlich der gleiche sein wie in der vorgelagerten Integer-Division. Statt Richtung Null bzw. nach unten abzuschneiden, rundet die erweiterte Routine jetzt zum nächstgelegenen Wert. Der SBC-Befehl kann entfallen, wenn der Rest im Programm weiter nicht verwendet wird.

    Ob das Runden jetzt notwendig oder sinnvoll ist, hängt vom Anwendungsfall ab. Bei vielen Geometrie-Algorithmen wird z.B. konstant nach unten gerundet und ggfs. vorher die Eingangsdaten so vorbehandelt, daß das Ergebnis stimmig aussieht. Richtig konkret fällt einem das auf, wenn man z.B. perspektivisch korrektes Texture-Mapping macht: da willst Du die berechneten Textur-Koordinaten immer nur nach unten runden, da Du sonst an zwei Rändern des projizierten Vierecks aus der Textur grob 'herausfällst'.

    Du solltest dir auch noch überlegen, was Du mit vorzeichenbehafteten Zahlen machst. ;) Gerade da wird die gerundete Version tendenziell eher lästig. Mit der Version, die Richtung 0 rundet, kannst Du im 2-Komplement die Berechung und die Vorzeichen immer so festlegen:

    |Quotient| = |Dividend| / |Divisor|

    sgn(Quotient) = sgn(Dividend) x sgn(Divisor)

    sgn(Rest) = sgn(Dividend)

    und es gilt immer(!): Dividend = Quotient x Divisor + Rest

    mit |...| := Betrag und sgn(...) := Vorzeichen.


    P.S. Du hast in Bitte melde dich an, um diesen Link zu sehen. am Anfang der Routine LDA #0 vergessen. Den Befehl kannst Du nicht einfach weglassen, sonst rechnet die Routine falsch!

    Warum entspricht das jetzt 22x65536 und nicht 23x0? (Ich weiß auch nicht wirklich, wofür das "x" steht?)

    23x65536, ich hab' das im Original-Beitrag gerade noch korrigieren können. Das "x" steht hier für Multiplikation.

    Das ist jetzt weniger wild als es aussieht, denn die Multiplikation mit einer 2er-Potenz in Binärschreibweise ist ja nur einfaches Bitschieben. Damit ist dann die 23 in 8:16-Fixpunktdarstellung und in Little Endian eben $00 $00 $17 - so wie Du das oben im Bild siehst, eingespeichert in $FB, $FC und $FD.

    Und so habe ich dann - beinahe - Pi neu entdeckt. :)

    22/7 ist eine gängige Näherung für pi - die war schon Archimedes bekannt. Die Chinesen haben mit 355/113 eine noch bessere Näherung gefunden, da sind dann schon 6 Nachkommastellen korrekt!

    Dividieren in Assembler, immer wieder ein schönes Thema! Vor allem, wenn eine Kommazahl herauskommt.

    Das ist jetzt nicht wirklich wild. Eine gängige 'Maßnahme' ist dann die Fixpunkt-Darstellung: die Zahlen werden mit einer 2er-Potenz multipliziert, alles links vom Radix-Punkt ist der ganzzahlige Anteil, alle Bits darunter der gebrochene Anteil.

    Das kann auch bei der Division mit Dividend und Divisor machen und muß dann nur ausknobeln, wo im Ergebnis der Radix-Punkt herauskommt. Umgekehrt, wenn das Format des Ergebnisses schon feststeht, muß man ermitteln, wie das Format des Dividenden und Divisors auszusehen hat.

    Da Du im Ergebnis ein Byte vor dem Radix-Punkt und zwei Bytes nach dem Radix-Punkt haben willst und hier im Beispiel der Divisor ganzzahlig ist, läßt sich das Ergebnis mit einer 24-Bit-durch-8-Bit-Integer-Division ausrechnen. Der Dividend ist dann 23x65536 - also, in 8:16-Darstellung. Bei Fixpunkt gibt die Zahl vor dem Doppelpunkt die Bits des ganzzahligen Anteils an (hier acht) und die Zahl nach dem Doppelpunkt die Bits des gebrochenen Anteils (hier 16).

    Ich nehme einfach mal meine Bitte melde dich an, um diesen Link zu sehen. her und erweitere die auf 24 Bit im Dividenden:

    Anstelle von #$07 bei dem CMP- und SBC-Befehl ist auch ein anderer Divisor möglich.

    Wenn Du jetzt in zp und zp+1 eine 0 schreibst, und in zp+2 die 23, dann sollte nach der Ausführung an gleicher Stelle das von dir erwartete Ergebnis $03 $49 $24 - allerdings in umgekehrter Reihenfolge, also little-endian! - herauskommen.

    P.S. JeeK hat bei der Originalfassung der Routine mal kräftig mitgeholfen. :D

    P.P.S. wolltest Du pi annähern? Dann wäre allerdings 22/7 der Bruch, den Du ausrechnen wolltest. ;)