Forum: Mikrocontroller und Digitale Elektronik abs() liefert negatives Ergebnis, WFT?


von Stefan F. (Gast)


Lesenswert?

Letztes Wochenende hängte sich mein C Programm auf einem STM32 auf, weil 
eine abs() Funktion ein scheinbar negatives Ergebnis lieferte.

Die Code-Stelle war:
1
void OLED::draw_line(uint_fast8_t x0, uint_fast8_t y0, uint_fast8_t x1, uint_fast8_t y1, tColor color)
2
{        
3
    // Algorithm copied from Wikipedia
4
    int_fast16_t dx = abs(x1 - x0);
5
    int_fast16_t sx = x0 < x1 ? 1 : -1;
6
    int_fast16_t dy = -abs(y1 - y0);      <----------- Hier
7
    int_fast16_t sy = y0 < y1 ? 1 : -1;
8
    int_fast16_t err = dx + dy;
9
    int_fast16_t e2;
10
11
    while (1)
12
    {
13
        draw_pixel(x0, y0, color);
14
        if (x0 == x1 && y0 == y1)
15
        {
16
            break;
17
        }
18
        e2 = 2 * err;
19
        if (e2 > dy)
20
        {
21
            err += dy;
22
            x0 += sx;
23
        }
24
        if (e2 < dx)
25
        {
26
            err += dx;
27
            y0 += sy;
28
        }
29
    }
30
}

-abs(y1 - y0);

y1 hatte den Wert 100 und y0 hatte den Wert 20. 100-20 ergibt 80.
-abs(80) müsste -80 ergeben, richtig? Tatsächlich war das Ergebnis aber 
eine unerwartete positive Zahl!

Der Fehler passierte deswegen, weil in der arduino.h die abs() Funktion 
als Makro definiert war:

#define abs(x) ((x)>0?(x):-(x))

Dieses Anwendung des Makros funktioniert nicht mit unsigned Integer, 
denn nach seiner Expansion entsteht:

-((y1-y0)>0?(y1-y0):-(y1-y0))

Setzen wir nun die konkreten Werte ein:

((100-80)>0?(100-80):-(100-80))
ergibt: -((100-80))

Da die beiden Variablen aber unsigned Integer waren, liefert diese 
Operation nicht das erwartet Ergebnis. Eklig, was? Wer ist Schuld?

Wollte ich nur mal erzählen.

By the way: Nachdem ich das erkannt habe,war die Lösung klar:
1
int_fast16_t dy = -abs(static_cast<int_fast8_t>(y1) - static_cast<int_fast8_t>(y0));

Oder ich hätte die Parameter der Funktion als signed Integer deklarieren 
sollen.

Beitrag #5194670 wurde von einem Moderator gelöscht.
von Carl D. (jcw2)


Lesenswert?

Stefan U. schrieb:
>
> Oder ich hätte die Parameter der Funktion als signed Integer deklarieren
> sollen.

Besser wär das. (frei nach Werner)

Die Differnz zweier unsigned int bleibt eben unsigned und eine "Zahl 
>=0" ist eben selten nicht ">0".

In Java löst man das ganz einfach: immer signed, aber Wehe wenn man 
wirklich 64Bits braucht. Java long hat über 0 nur 63.

von Dr. Sommer (Gast)


Lesenswert?

Stefan U. schrieb:
> Da die beiden Variablen aber unsigned Integer waren, liefert diese
> Operation nicht das erwartet Ergebnis.

Was hast du denn erwartet? Dass abs automatisch einen signierten Integer 
zurück gibt, wenn du unsignierte eingibst? Dass "-" auf unsignierten 
Integern sinnvoll arbeitet?

Es ist aber in der Tat seltsam dass Arduino "abs" als Makro definiert. 
Man hätte auch einfach "using std::abs;" schreiben können, dann würde 
automatisch die sauberere Funktion aus der Standardbibliothek genutzt.

Stefan U. schrieb:
> Letztes Wochenende hängte sich mein C Programm auf einem STM32 auf
Es ist offensichtlich ein C++-Programm...

Stefan U. schrieb:
> By the way: Nachdem ich das erkannt habe,war die Lösung
> klar:int_fast16_t dy = -abs(static_cast<int_fast8_t>(y1) -
> static_cast<int_fast8_t>(y0));
Na hoffentlich sind y1 und y0 niemals >= 128...

von Stefan F. (Gast)


Lesenswert?

> Na hoffentlich sind y1 und y0 niemals >= 128...

Stimmt, danke für den Hinweis. Sie sind tatsächlich nie größer als 128. 
Oha, aber 128 sind schon möglich und sinnvoll.

Da muss ich noch etwas anpassen. int_fast16_t wäre sowieso logischer, 
weil ich das Ergebnis ja auch in einen int_fast16_t speichere.

> Was hast du denn erwartet? Dass abs automatisch einen signierten
> Integer zurück gibt

Ehrlich gesagt ja. Und der Entwickler, der meine Kopiervorlage bei 
Wikipedia veröffentlichte, ging wohl auch davon aus. Wenn abs() eine 
Funktion gewesen wäre und als Rückgabewert einen unsigned Integer gehabt 
hätte, dann hätte es auch bei ihm nicht funktioniert.

Ich habe stundenlang daran gerätselt, ich war am Sonntag echt sehr 
genervt.

von Dr. Sommer (Gast)


Lesenswert?

Stefan U. schrieb:
> Ehrlich gesagt ja.
Und was wäre dann mit Zahlen die in den unsignierten, aber nicht in den 
signierten Integer passen? Mit C++ templates könnte man so etwas 
implementieren, mit C-Makros nicht.

Stefan U. schrieb:
> Ich habe stundenlang daran gerätselt, ich war am Sonntag echt sehr
> genervt.
Hättest du std::abs genommen, hättest du direkt einen Compilerfehler 
bekommen ("call of overloaded 'abs(uint_fast8_t)' is ambiguous"). Aber 
aus irgendwelchen Gründen sind ja alle so versessen darauf für alles und 
jedes Makros zu nutzen (Hier: die Arduino-Entwickler)!

von Rolf M. (rmagnus)


Lesenswert?

Stefan U. schrieb:
> Da die beiden Variablen aber unsigned Integer waren, liefert diese
> Operation nicht das erwartet Ergebnis.

Wozu nimmt man denn auch den Absolutwert von vorzeichenlosen Integern? 
Die sind doch sowieso schon positiv.

von (prx) A. K. (prx)


Lesenswert?

In Zeilen wie
  int_fast16_t dy = -abs(y1 - y0);
mit y0,y1 vom Typ uint_fast8_t wird angenommen, dass die Subtraktion in 
"int" stattfindet. Tut sie bei AVR und x86, weil auf diesen Plattformen 
uint_fast8_t identisch mit uint8_t ist. Bei ARM ist das jedoch nicht der 
Fall, da entspricht das unsigned und die Subtraktion erfolgt unsigned.

von Carl D. (jcw2)


Lesenswert?

Rolf M. schrieb:
> Stefan U. schrieb:
>> Da die beiden Variablen aber unsigned Integer waren, liefert diese
>> Operation nicht das erwartet Ergebnis.
>
> Wozu nimmt man denn auch den Absolutwert von vorzeichenlosen Integern?
> Die sind doch sowieso schon positiv.

Die Differenz zweier vorzeichenlosen Zahlen kann schon negativ sein. Nur 
muß man diesen Rechenwunsch auch korrekt formulieren. Es ist also 
eigentlich kein abs() Problem. Abgesehen von der 
"Falltür"-Implementierung als Makro.

Lustigerweise hatten wir vor Kurzem hier die Diskussion über millis() 
und warum 2 millis()-Werte subtrahiert auch bei Überlauf den korrekten 
Wert ergeben. Eben weil sie unsigned (long) sind und dafür 
"Modulo"-Arithmetik vorgesehen ist.
Hätte der TO also "...(int)y1 - (int)y0..." geschrieben, wäre alles in 
Butter.

von Achim (Gast)


Lesenswert?

Sohnes/unsigned ist mit das größte Problem in C.

Überlaufen sind nur unsigned erlaubt,
Die maximale negative Zahl gibt es nicht in positiv
Bei < oder > und unsigned auf einer Seite ist -1 die größte Zahl.

Und einen Cast, der nur signed/unsigned ändert, gibt es leider nicht.

Dieser Fall ist meistens Dummheit, wenn die Compiler-Warnung als Unsinn 
ignoriert wird. ("Hä, immer true, quatsch")

von Walter T. (nicolas)


Lesenswert?

Carl D. schrieb:
> Hätte der TO also "...(int)y1 - (int)y0..." geschrieben, wäre alles in
> Butter.

Oder auch intuitiv (int8_t)(y1 - y0), das funktioniert sogar noch bei 
Überlauf.

von Stefan F. (Gast)


Lesenswert?

Mein Fehler war, dass ich die Aufrufparameter einfach auf unsigned 
Integer geändert habe ohne über die Folgen nachzudenken.

Als ich dann über die Folgen nachdachte, entpuppte sich das abs() Makro 
als weiterer Fallstrick.

So einen Fehler mache ich hoffentlich nicht nochmal.

von A. S. (Gast)


Lesenswert?

Stefan U. schrieb:
> habe ohne über die Folgen nachzudenken

aber dafür hat man doch die Warnungen an. Wie kam es, dass die nicht 
ansprangen?

von Der Andere (Gast)


Lesenswert?

Dr. Sommer schrieb:
> einen signierten Integer

Was zum Geier soll ein signierter Integer sein.
Die deutsche Übersetzung von "signed" heisst "vorzeichenbehaftet".
"Signiert" kommt von "Signatur" und bedeutet was völlig anderes.

von Dr. Sommer (Gast)


Lesenswert?

Der Andere schrieb:
> Was zum Geier soll ein signierter Integer sein.
Du hast offensichtlich verstanden was gemeint war. Geh dich doch bei 
deinem betreuenden Germanisten ausheulen.

von Stefan F. (Gast)


Lesenswert?

> aber dafür hat man doch die Warnungen an.

Ja, waren alle an.

> Wie kam es, dass die nicht ansprangen?

Weiss ich nicht.

von Dr. Sommer (Gast)


Lesenswert?

Achim S. schrieb:
> aber dafür hat man doch die Warnungen an. Wie kam es, dass die nicht
> ansprangen?

Welche Warnung soll hier denn erscheinen? GCC liefert mit "-Wall -Wextra 
-pedantic -Wconversion" jedenfalls keine.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wäre das Makro mit >= statt > definiert worden, käme es mit
-Wtype-limits (enthalten in -Wextra) zur folgender Warnung:

1
  comparison of unsigned expression >= 0 is always true

Aber eben nur dann.

von A. S. (Gast)


Lesenswert?

Yalu X. schrieb:
> Wäre das Makro mit >= statt > definiert worden

Oh, alle Implementierungen die wir hier haben (als #define) sehen so 
aus:
1
#define abs(a)  (((a) < 0) ? -(a) : (a))

Wäre ja auch Unsinn, bei x==0 noch zu negieren, wie in arduino.h. Ich 
sehe bei signed-Werten auch keinen generellen Vorteil beim Vergleich auf 
>0 vs. <0, eher im Gegenteil.

Vielleicht wollte der Herr arduino das abs bei unsinniger Verwendung mit 
unsigned "fehlerfrei" bekommen.

von Der Andere (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Du hast offensichtlich verstanden was gemeint war. Geh dich doch bei
> deinem betreuenden Germanisten ausheulen.

Das ich es verstanden habe liegt an meiner Fähigkeit.
Das du nicht in der Lage bist korrekt zu formulieren an deiner 
Unfähigkeit.
Und dass du eine Korrektur auch noch lächerlich machen willst an deiner 
Ignoranz
:-p

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.