Forum: Mikrocontroller und Digitale Elektronik Ist x>>16 das Gleiche wie x/65536?


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Mike (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich programmiere gerade ein simples digitales Filter auf einem ARM 
Cortex M0+ (SAMD11). Der Compiler ist gcc. Dabei muss des öfteren durch 
65536 dividiert werden, um den Zahlenbereich zu begrenzen. Korrekt würde 
es also so lauten:
1
int32_t x;
2
...
3
x /= 65536;

Allerdings ist die Division eine aufwändige Operation, da der ARM sie 
iterativ berechnen muss, denn es gibt keinen Maschinen-Divisionsbefehl. 
Theoretisch würde es genügen, die beiden oberen Bytes von x in die 
beiden niederwertigsten Bytes zu verschieben:
1
int32_t x;
2
...
3
x >>= 16;

Doch ist garantiert, dass bei negativem x von links Einsen eingeschoben 
werden, so dass das Ergebnis negativ bleibt? Die ANSI-C-Dokumentationen 
schweigen sich darüber aus. Mit gcc scheint es zu funktionieren, aber 
wie sieht es bei anderen Compilern aus? Gibt es eine saubere 
Möglichkeit, einen arithmetischen Shift erzwingen?

von Route_66 H. (route_66)


Bewertung
-2 lesenswert
nicht lesenswert
Mike schrieb:
> Die ANSI-C-Dokumentationen
> schweigen sich darüber aus.

Ausprobieren ist dir zu umständlich?
Hier Hobbyprogrammierer belästigen und eine unzuverlässige Info ist ja 
viiiieeel wertvoller.

: Bearbeitet durch User
von Peter II (Gast)


Bewertung
2 lesenswert
nicht lesenswert
Route 6. schrieb:
> Ausprobieren ist dir zu umständlich?

lesen zu umständlich?

> Mit gcc scheint es zu funktionieren

von Noch einer (Gast)


Bewertung
2 lesenswert
nicht lesenswert
Hast du kontrolliert, ob "x /= 65536" eine aufwendige Division macht? 
Normalerweise kennt der GCC-Optimizer mehr Tricks, als bei einem 
Programmiere aus den Mathe Vorlesungen hängen geblieben ist.

von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
Mike schrieb:
> Doch ist garantiert, dass bei negativem x von links Einsen eingeschoben
> werden, so dass das Ergebnis negativ bleibt?

Nein. Offiziell nicht. Ich kenne aber keine Plattform, bei der es anders 
ist.

Allerdings ist das Ergebnis bei negativem Nenner auch bei 
reingeschobenem Vorzeichen verschieden, wenn der Rest der Division nicht 
0 ist.

: Bearbeitet durch User
von Leo C. (rapid)


Bewertung
0 lesenswert
nicht lesenswert
Division:
1
  mov  r3, #65536
2
  sdiv  r0, r0, r3

Shift:
1
  asrs  r0, r0, #16

gcc version 6.3.1

von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
A. K. schrieb:
> Allerdings ist das Ergebnis bei negativem Nenner auch bei
> reingeschobenem Vorzeichen verschieden, wenn der Rest der Division nicht
> 0 ist.

Allerdings ist das Ergebnis bei negativem Zähler auch bei
reingeschobenem Vorzeichen verschieden, wenn der Rest der Division nicht
0 ist.

von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
Leo C. schrieb:
> Division:
>
1
>   mov  r3, #65536
2
>   sdiv  r0, r0, r3
3
>

gcc 4.7.1, -O1, amd64:
        leal    65535(%rdi), %eax
        testl   %edi, %edi
        cmovns  %edi, %eax
        sarl    $16, %eax

von M. K. (sylaina)


Bewertung
0 lesenswert
nicht lesenswert
Mike schrieb:
> Allerdings ist die Division eine aufwändige Operation, da der ARM sie
> iterativ berechnen muss, denn es gibt keinen Maschinen-Divisionsbefehl.

Eigentlich sollte jeder halbwegs moderne Compiler eine 
Division/Multiplikation auf Basis einer Zweipotenz erkennen und in eine 
Schiebeoperation umwandeln.

von Detlev T. (detlevt)


Bewertung
0 lesenswert
nicht lesenswert
Ich habe es einmal kurz getestet. Selbst ohne Optimierung (-O0) macht 
der gcc aus (int32_t) /= 65536:
1
141b        asrs  r3, r3, #16
Also einen Shift-Befehl, der das Vorzeichen erhält.

von (prx) A. K. (prx)


Bewertung
1 lesenswert
nicht lesenswert
M. K. schrieb:
> Eigentlich sollte jeder halbwegs moderne Compiler eine
> Division/Multiplikation auf Basis einer Zweipotenz erkennen und in eine
> Schiebeoperation umwandeln.

Yep. Nur ist sdiv kürzer als die Ersatzsequenz.

Cortex M3, gcc 4.8.1, -Os:
        mov     r3, #65536
        sdiv    r0, r0, r3
und bei -O1:
        add     r3, r0, #65280
        adds    r3, r3, #255
        bics    r0, r0, r0, asr #32
        it      cs
        movcs   r0, r3
        asrs    r0, r0, #16

von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
Detlev T. schrieb:
> Ich habe es einmal kurz getestet. Selbst ohne Optimierung (-O0) macht
> der gcc aus (int32_t) /= 65536:
>
1
> 141b        asrs  r3, r3, #16
2
>
> Also einen Shift-Befehl, der das Vorzeichen erhält.

Gibt bei -3 / 2 ein falsches Ergebnis. Bissel mehr ist also nötig, siehe 
oben.

: Bearbeitet durch User
von Mike (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Leo C. schrieb:
> Division:  mov  r3, #65536
>   sdiv  r0, r0, r3
>
> Shift:  asrs  r0, r0, #16
>
> gcc version 6.3.1

sdiv gibt es nicht beim Cortex M0+:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0484b/CHDCICDF.html

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Detlev T. schrieb:
> Ich habe es einmal kurz getestet. Selbst ohne Optimierung (-O0) macht
> der gcc ... einen Shift-Befehl, der das Vorzeichen erhält.
Das ist prinzipiell falsch, weil in die falsche Richtung "gerundet" 
wird.
Siehe http://codepad.org/s9bcOmkk oder http://codepad.org/8NMZ194I

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
Kurioserweise ist der gcc 6.3.1 Code für den Cortex M0
        asr     r3, r0, #31
        lsr     r3, r3, #16
        add     r0, r3, r0
        asr     r0, r0, #16
besser als der zeitoptimierte Code für den Cortex M3
        add     r3, r0, #65280
        adds    r3, r3, #255
        bics    r0, r0, r0, asr #32
        it      cs
        movcs   r0, r3
        asrs    r0, r0, #16
obwohl der M3 ein Superset des M0 ist.

: Bearbeitet durch User
von Mike (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ich habe es mal mit meinem Compiler für die unterschiedlichen 
Optimierungsstufen ausprobiert. Was das Hin-und Hergeschiebe bei -O(1) 
macht, überschaue ich im Moment nicht, da ich mich mit Cortex-Assembler 
nicht gut auskenne. Ab -O(2) wird immer arithmetisch geschoben.
Das Rundungsverhalten ist anscheinend nicht gleich. Arithmetisch ">>" 
rundet ja immer zur nächstkleineren Zahl. "/" scheint dagegen zur Null 
hinzurunden, wenn ich mich richtig erinnere. Der Compiler scheint sich 
wohl nicht darum zu scheren.



-O(0)
1
  x /= 65536;
2
 218:  687b        ldr  r3, [r7, #4]
3
 21a:  2b00        cmp  r3, #0
4
 21c:  da02        bge.n  224 <main+0x38>
5
 21e:  4a11        ldr  r2, [pc, #68]  ; (264 <main+0x78>)
6
 220:  4694        mov  ip, r2
7
 222:  4463        add  r3, ip
8
 224:  141b        asrs  r3, r3, #16
9
 226:  607b        str  r3, [r7, #4]
10
  y >>= 16;
11
 228:  683b        ldr  r3, [r7, #0]
12
 22a:  141b        asrs  r3, r3, #16
13
 22c:  603b        str  r3, [r7, #0]

-O(1)
1
  x /= 65536;
2
 202:  17eb        asrs  r3, r5, #31
3
 204:  041b        lsls  r3, r3, #16
4
 206:  0c1b        lsrs  r3, r3, #16
5
 208:  195b        adds  r3, r3, r5
6
 20a:  141b        asrs  r3, r3, #16
7
  PORT->Group[0].OUT.reg = x;
8
 20c:  6123        str  r3, [r4, #16]
9
  y >>= 16;
10
 20e:  1400        asrs  r0, r0, #16

-O(s), -O(2) und -O(3)
1
        y >>= 16;
2
 1e8:  1400        asrs  r0, r0, #16
3
  x /= 65536;
4
 1ea:  141b        asrs  r3, r3, #16

von (prx) A. K. (prx)


Bewertung
0 lesenswert
nicht lesenswert
Mike schrieb:
> Was das Hin-und Hergeschiebe bei -O(1)
> macht, überschaue ich im Moment nicht,

Der Compiler aber schon ...

> Der Compiler scheint sich wohl nicht darum zu scheren.

... weshalb diese Aussage etwas "mutig" ist. Denn das "Hin-und 
Hergeschiebe" dient eben dazu, die Rundung richtig durchzuführen.

Prinzip für x / (1<<N):
  if (x < 0)
     x += (1<<N)-1;
  x >>= N;
Genau das macht er in der nicht optimierten Version.

Die optimierte Version für den Cortex M0 erzeugt einen zu addierenden 
Wert 0 oder (1<<N)-1 auf recht clevere Art.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
Man sollte sich in diesem Zusammenhang auch mal Gedanken darüber machen,
welche Rundung man bei Integer-Division überhaupt bevorzugt: zur 0 hin
(wie in C) oder Richtung -∞ (wie in Python). Die letztere ist für mich
die logischere und in den meisten Anwendungen auch die geeignetere.

Reguläre Integer-Divisionen mit negativen Operanden verwende ich
deswegen in C so gut wie nie. Für Skalierungszwecke wie bspw. bei der
Festkommaarithmetik (die ich aber nur selten vorzeichenbehaftet
benötige) verwende ich ausschließlich Shift-Operationen. Dass der
>>-Operator für negative linke Operanden implementation-defined ist,
stört mich dabei nicht so sehr, da diesbezüglich alle von mir bisher
eingesetzten Compiler dasselbe Verhalten zeigen.

Für die vorliegende Anwendung in einem digitalen Filter (die ich
allerdings nicht im Detail kenne) dürfte die Shift-Operation ebenfalls
die bessere Wahl sein.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Yalu X. schrieb:
> Man sollte sich in diesem Zusammenhang auch mal Gedanken darüber machen,
> welche Rundung man bei Integer-Division überhaupt bevorzugt: zur 0 hin
> (wie in C) oder Richtung -∞ (wie in Python). Die letztere ist für mich
> die logischere und in den meisten Anwendungen auch die geeignetere.
Wobei "Runden" im Fall hier der falsche Begriff ist: es geht eher um ein 
korrektes "Abschneiden". Es ist ein wenig verwirrend und überraschend, 
wenn das Ergebnis der "Division" 3/16 den Wert 0 ergibt, aber -3/16 den 
Wert -1

http://codepad.org/fHApZAda

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Angehängte Dateien:

Bewertung
3 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Es ist ein wenig verwirrend […]

für den Kaufmann: ja
für den Mathematiker: nein
für den Ingenieur oder Informatiker: kommt darauf an :)

Im Anhang habe ich mal die verschiedenen Divisionsmöglichkeiten im
Hinblick auf die vorliegende Aufgabenstellung, nämlich die Skalierung
von Signalen, vergleichend dargestellt.

In allen drei Fällen wird von einem Sinussignal mit der Amplitude 120
ausgegangen. Damit die Werte in eine 8-Bit- Variable passen, soll das
Signal um den Faktor 16 herunterskaliert werden. In den drei Diagrammen
ist das ohne Diskretisierung skalierte Signal jeweils in grüner Farbe
eingezeichnet.

Im ersten Beispiel wird dazu die gewöhnliche Integer-Division genommen.
An den Nulldurchgängen entstehen leichte Unregelmäßigkeiten ähnlich den
Übernahmeverzerrungen eines schlechten Gegentaktendstufe. Außerdem ist
die Amplitude um 0,5 LSB zu klein.

Im zweiten Beispiel wird die Division durch eine Shift-Operation
realisiert. Die Nulldurchgangsverzerrungen sind verschwunden und auch
die Amplitude stimmt. Allerdings hat das Signal jetzt einen Offset von
-0,5 LSB, der bei Filteranwendungen aber über einen Koppelkondensator am
Ausgang eliminiert wird und deswegen meist akzeptabel ist.

Man kann diesen Offset aber auch auf digitaler Seite korrigieren, wie
das dritte Beispiel zeigt. Dazu wird vor dem Shift einfach der halbe
Skalierungfaktor zum Signal addiert.

: Bearbeitet durch Moderator

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.