Forum: Mikrocontroller und Digitale Elektronik 8-bit-AVR: int32 to uint32


von Leo B. (luigi)


Lesenswert?

Servus zusammen,

ich steh vermutlich grad nur mitten auf der Leitung, aber folgendes 
Problem:

Ich rechne auf einem 8-bit AVR ATmega, und habe einen 32-bit int mit 
Vorzeichen, dessen Betrag ich ermitteln möchte.
Nun ist mir klar, dass das Ergebnis (also der Betrag) ein unsigned 
32-bit sein muss, da ja INT_MAX eins kleiner ist als -INT_MIN.
Mein Vorgänger hat mir im Code jetzt etwas hinterlassen, das vom gcc zu 
ziemlich viel Programmcode aufgeblasen wird:
1
static uint32_t abs( int32_t a )
2
{
3
  if( a < 0 )
4
  {
5
    // -INT_MIN ist idR. größer als INT_MAX und nicht mehr
6
    // als int darstellbar! Man muss daher bei der Bildung
7
    // des Absolutbetrages aufpassen.
8
    return -( (uint32_t)(-(a+1)) + 1 );
9
  }
10
  return (uint32_t)a;
11
}

und nun stehe ich auf dem Schlauch, warum er das so komplex macht...
Ergibt es nicht das selbe einfach folgendes zu schreiben?
1
static uint32_t abs( int32_t a )
2
{
3
  if( a < 0 )
4
    return (uint32_t)(-a);
5
  return (uint32_t)a;
6
}

Irgendwie leuchtet es mir nicht ganz ein warum das so komplex sein soll.

Vielen Dank
Leo

von Eric B. (beric)


Lesenswert?

Dein Vorgänger hat hier versucht den Fall a == 0x80000000 einzufangen, 
wo es nichts einzufangen gab :-/ Gegen deiner Lösung ist m.E. nichts 
einzuwenden.

Noch kürzer wäre:
1
static uint32_t abs( int32_t a )
2
{
3
  return (uint32_t)((a < 0)? -a: a);
4
}

von Tassilo H. (tassilo_h)


Lesenswert?

Hmm, aber wird -a nicht als signed 32-bit berechnet? Und wenn ich mich 
recht entsinne, ist das Überlaufverhalten bei signed integer nicht 
definiert. D.h. abhängig von der Implementierung kann das gutgehen oder 
auch nicht. Und noch gemeiner: Selbst wenn das gutgeht, wenn man eine 
Variable übergibt, kann das trotzdem noch schiefgehen, wenn man einen 
Ausdruck übergibt, von dem der Compiler weiß, daß es INT32_MIN ist. Dann 
kann der Optimierer jedwede Berechnung sein lassen und das Ergebnis als 
beliebig annehmen (d.h. i.a. so, daß am meisten anderer Code eingespart 
werden kann).

Besser evtl. so:
1
if (a == INT32_MIN) {
2
  return ((uint32_t)INT32_MAX)+1ul;
3
} else {
4
  return a < 0 ? -a : a;
5
}

: Bearbeitet durch User
von M. K. (sylaina)


Lesenswert?

Tassilo H. schrieb:
> Hmm, aber wird -a nicht als signed 32-bit berechnet? Und wenn ich mich
> recht entsinne, ist das Überlaufverhalten bei signed integer nicht
> definiert.

Und was soll da überlaufen? Wenn der übergebene signed int32 kleiner 0 
ist wird schlicht der übergebene signed int32 mit -1 multipliziert und 
jeder Compiler heute weiß, dass er dafür lediglich nur ein einziges bit 
toggeln muss.

von Carl D. (jcw2)


Lesenswert?

Michael K. schrieb:
> Tassilo H. schrieb:
>> Hmm, aber wird -a nicht als signed 32-bit berechnet? Und wenn ich mich
>> recht entsinne, ist das Überlaufverhalten bei signed integer nicht
>> definiert.
>
> Und was soll da überlaufen? Wenn der übergebene signed int32 kleiner 0
> ist wird schlicht der übergebene signed int32 mit -1 multipliziert und
> jeder Compiler heute weiß, dass er dafür lediglich nur ein einziges bit
> toggeln muss.

Hoffentlich weiß der Compiler eher, daß er die anderen 31 auch togglen 
muß und zudem noch 1 addieren.

von Sebastian V. (sebi_s)


Lesenswert?

Überlaufverhalten von signed int ist nicht definiert. Ich habe in der 
MPIR Library (dem Fork von GMP) mal folgendes gesehen:
1
static uint32_t abs( int32_t a )
2
{
3
  if( a < 0 )
4
    return -(uint32_t)(a);
5
  return (uint32_t)a;
6
}
Meiner Meinung nach ist das wohldefiniert, da zuerst die Konvertierung 
von signed zu unsigned passiert (ggf. mit Überlauf) und dann eine 
Negation von dem unsigned Wert, ebenfalls mit wohldefiniertem 
Überlaufverhalten.

von Tassilo H. (tassilo_h)


Lesenswert?

a) es ist kein einzelnes bit, sondern das Zweierkomplement
b) (-INT32_MIN) ist als int32_t nicht darstellbar, da -INT32_MIN = 
INT32_MAX+1. Die Rechnung wird aber erstmal als int32_t ausgeführt. 
Klar, wenn man das alles ignoriert, wird es auf den meisten 
Prozessorarchitekturen gutgehen, da das Zweierkomplement wieder 
0x80000000 ergibt, was nach uint32_t gecastet dann stimmt. 
Nichtsdestotrotz hat man zwischendrin etwas nicht definiertes, da 
-INT32_MIN eben nicht in ein int32_t passt.
Wenn der Compiler das merkt und man die Funktion mal mit zur Compilezeit 
bekannten INT32_MIN aufruft, könnte der Optimizer hingehen uns sagen, 
das ist eh alles nicht definiert, also laß ich jetzt allen Code weg, der 
mit dem Ergebnis weiterrechnet und nehme als Ergebnis irgendwas an. Muß 
nicht passieren, kann aber. Sowas gibt wundervoll schwer zu findende 
Bugs in Code, der eigentlich immer schon funktioniert hat...

von MagIO (Gast)


Lesenswert?

Ja klar, 1 bit toggeln ;o)

-1 = %11111111111111111111111111111111
Toggelt man jetzt nur das Sign-bit wird daraus
%01111111111111111111111111111111 = INT32MAX

Das was man machen muss nennt sich Zweierkomplement:
Alle bits invertieren und eins dazuzählen.

von Tassilo H. (tassilo_h)


Lesenswert?

Sebastian V. schrieb:
> Überlaufverhalten von signed int ist nicht definiert. Ich habe in der
> MPIR Library (dem Fork von GMP) mal folgendes gesehen:
>
1
static uint32_t abs( int32_t a )
2
> {
3
>   if( a < 0 )
4
>     return -(uint32_t)(a);
5
>   return (uint32_t)a;
6
> }
> Meiner Meinung nach ist das wohldefiniert, da zuerst die Konvertierung
> von signed zu unsigned passiert (ggf. mit Überlauf) und dann eine
> Negation von dem unsigned Wert, ebenfalls mit wohldefiniertem
> Überlaufverhalten.

Gute Lösung, stimmt, das ist definiert. -(uint32_t)a ist definiert als 
UINT32_MAX-a+1, was das richtige Ergebnis liefert.

von B. S. (bestucki)


Lesenswert?

Leo B. schrieb:
> da ja INT_MAX eins kleiner ist als -INT_MIN.

Nicht zwingend. Nach Standard kann die Plattform auch das Einer- statt 
dem Zweierkomplement benutzen, dann ist der Betrag von MIN und MAX 
identisch. Wenn man also portabel programmieren will, muss man annehmen, 
dass z.B. ein signed char einen Wertebereich von -127 bis +127 hat, die 
mögliche -128 muss man natürlich trotzdem beachten. Ich weiss, ist 
Haarspalterei, die meisten Plattformen benutzen das Zweierkomplement.

Michael K. schrieb:
> jeder Compiler heute weiß, dass er dafür lediglich nur ein einziges bit
> toggeln muss.

Nur beim Einerkomplement. Beim Zweierkomplement siehts anders aus.

Michael K. schrieb:
> Und was soll da überlaufen?

Z.B. bei einem 16Bit int:
1
-(-32768)
Die Zahl +32768 existiert bei einem solchen int nicht => Überlauf.

Tassilo H. schrieb:
> Gute Lösung, stimmt, das ist definiert. -(uint32_t)a ist definiert als
> UINT32_MAX-a+1, was das richtige Ergebnis liefert.

Nein. Ein Cast einer negativen Zahl in einen unsigned Typen ist 
undefiniertes Verhalten. Edit: Moment, lese gerade noch, bin mir nicht 
mehr ganz sicher.

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

be s. schrieb:
> Nein. Ein Cast einer negativen Zahl in einen unsigned Typen ist
> undefiniertes Verhalten. Edit: Moment, lese gerade noch, bin mir nicht
> mehr ganz sicher.

Ist definiert:

6.3.1.3 Signed and unsigned integers
[...]
Otherwise, if the new type is unsigned, the value is converted by 
repeatedly adding or subtracting one more than the maximum value that 
can be represented in the new type until the value is in the range of 
the new type.

von Tassilo H. (tassilo_h)


Lesenswert?

Der cast ist definiert:

"the value is converted by repeatedly adding or subtracting one more 
than the maximum value that can be represented in the newtype until the 
value is in the range of the newtype."

Beim Zweierkomplement ändert sich also nix am Bitmuster und alle obigen 
Annahmen stimmen. Auf einer Einerkomplementmaschine stimmt das natürlich 
nicht, aber das ist jetzt eher unüblich.

von B. S. (bestucki)


Lesenswert?

Danke für die Berichtigung. Umgekehrt ists abhängig von der 
Implementierung (Kapitel: Implementation-defined behavior):
> The result of, or the signal raised by, converting an integer to a
> signed integer type when the value cannot be represented in an object
> of that type (6.3.1.3).

Hatte wohl das im Kopf. Gerade wieder mal die Liste von undefiniertem 
Verhalten überflogen, die ist einfach zu lang...

von Klaus (Gast)


Lesenswert?

Leo B. schrieb:
> und habe einen 32-bit int mit
> Vorzeichen, dessen Betrag ich ermitteln möchte.

> abs() liefert den absoluten Wert einer Zahl: Bei einer negativen
> Zahl wird das Vorzeichen vertauscht, eine positive Zahl wird
> unverändert zurückgegeben.

Funktioniert auf jeder Plattform und wird auch von jedem als das 
erkannt, was es sein soll. Alles andere ist maximum obfuscation.

MfG Klaus

von B. S. (bestucki)


Lesenswert?

Klaus schrieb:
>> abs() liefert den absoluten Wert einer Zahl: Bei einer negativen
>> Zahl wird das Vorzeichen vertauscht, eine positive Zahl wird
>> unverändert zurückgegeben.
>
> Funktioniert auf jeder Plattform und wird auch von jedem als das
> erkannt, was es sein soll. Alles andere ist maximum obfuscation.

Beziehst du dich auf die Version aus stdlib.h? Denn für diese gelten 
folgende Einschränkungen:
> In C, only the int version exists.
> If the result cannot be represented by the returned type (such as
> abs(INT_MIN) in an implementation with two's complement signed values),
> it causes undefined behavior.
http://www.cplusplus.com/reference/cstdlib/abs/

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

In C90 gibt es labs und ab C99 auch llabs.  Damit sollten 32-Bit und 
64-Bit abs doch nun wirklich kein Problem mehr sein...

von B. S. (bestucki)


Lesenswert?

Johann L. schrieb:
> In C90 gibt es labs und ab C99 auch llabs.  Damit sollten 32-Bit und
> 64-Bit abs doch nun wirklich kein Problem mehr sein...

Jedoch mit der gleichen Einschränkung von undefiniertem Verhalten und 
diese soll hier eliminiert werden. Ausserdem, welche der drei Funktionen 
benutzt du, wenn du als Argument einen uint32_t hast und portabel 
programmieren willst? Das ist ein übler Nachteil, der daraus entstanden 
ist, den Typen eine variable Breite zuordnen zu dürfen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...außerdem sollte man einer Funktion nicht den gleichen Namen geben wie 
eine  Standard-Funktion (abs)

http://linux.die.net/man/3/abs

von Leo B. (luigi)


Lesenswert?

Vielen herzlichen Dank,
habe wieder viel gelernt!
Und die Frage dürfte beantwortet sein. Danke!

Grüße Leo

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Leo B. schrieb:

> static uint32_t abs( int32_t a )
> {
>   if( a < 0 )
>   {
>     // -INT_MIN ist idR. größer als INT_MAX und nicht mehr
>     // als int darstellbar! Man muss daher bei der Bildung
>     // des Absolutbetrages aufpassen.
>     return -( (uint32_t)(-(a+1)) + 1 );
>   }
>   return (uint32_t)a;
> }

IMHO müsste das auch per (~a)+1 funktionieren ... (2er-Komplement 
nehmen)

Oder simpler einfach -a wie in den Beiträgen vorher schon erklärt

Die Rechnung von deinem Kollegen sieht sonderbar aus ... Er wusste wohl, 
dass -a das zweierkomplement nimmt und versuchte dann per -(a+1) das zu 
kompensieren. Und das hat er gleich zweimal gemacht, nachdem er es in 
einen uint32 gecastet hat.

Seltsam das ist ...

von Daniel A. (daniel-a)


Lesenswert?

Um mochmal zur Ursprungsfunktion zu kommen:
1
static uint32_t abs( int32_t a )
2
{
3
  if( a < 0 )
4
  {
5
    // -INT_MIN ist idR. größer als INT_MAX und nicht mehr
6
    // als int darstellbar! Man muss daher bei der Bildung
7
    // des Absolutbetrages aufpassen.
8
    return -( (uint32_t)(-(a+1)) + 1 );
9
  }
10
  return (uint32_t)a;
11
}

Das ist Identisch zu:
1
static uint32_t abs( int32_t a ){
2
  return a;
3
}

Begründung: Den cast kann man sich wegdenken, da dieser keinen Einfluss 
auf das Ergebniss hat, Wenn man dann ausklammert heben sich die 
Additionen auf, und die Subtraktionen auch.

Ich empfehle automatisierte Tests!

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.