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:
-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:
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.
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...
> 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.
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)!
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.
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.
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.
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")
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.
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.
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.
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.
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.
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.
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