Forum: Compiler & IDEs Integer Promotion - mal wieder


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,
ich bin mal wieder bei einem klassischen Integer-Promotion-Problem. Ich 
habe zwei Variablen vom Typ "uint_fast24_t", der -je nach Plattform- als 
24-Bit-Integer oder als 32-Bit-Integer ausgebildet ist, von denen ich 
vorzeichenrichtig die Differenz bilden will. Etwa so:
1
// Timer wird in Interrupt hochgezählt
2
int_fast24_t gl_timer_ms; 
3
4
int_fast16_t howLongDoIneed(void)
5
{
6
    int_fast16_t diff;
7
    uint_fast24_t storetime;
8
9
    storetime = gl_times_ms;
10
11
    // Macht eine wahnsinnig lange Rechnung, die mehrere Millisekunden dauert
12
    // ...
13
    // ...
14
    // ...
15
16
    diff = storetime - gl_times_ms;
17
    return diff;
18
}
Auf einer 32-Bit-Plattform ist uint_fast24_t == uint32_t. Damit 
funktioniert der Überlauf korrekt. Integer Promotion findet zwischen 
zwei uint32_t nicht statt, sodaß sich bei der Zuweisung in einen signed 
integer immer das richtige Vorzeichen ergibt.

Auf einer 8-Bit-Plattform (AVR) scheint es auch richtig zu funktionieren 
- jedenfalls  beim Ausprobieren. Hier ist der uint_fast24_t auch 
wirklich 24 Bit breit. Also findet keine Integer Promotion statt und die 
Differenzbildung findet auf 24Bit Breite statt und die 
vorzeichenrichtige Zuweisung an "diff" scheint mir 
implementierungsabhängig zu sein.

Aber wie richtet man es richtig ein, daß der gewünschte Zweck 
--vorzeichenrichtige Differenz zweier uint24_t -- plattformübergreifend 
korrekt ausgeführt wird? Die Variable "diff" wird später in der Breite 
16 Bit benötigt.

Viele Grüße
W.T.

von Hans (Gast)


Lesenswert?

Wozu denn die vorzeichenbehafteten Datentypen? Die Zeitdifferenz kann 
nicht negativ werden, also kannst Du für alles uint_fast24_t nehmen.

Wenn es in gl_timer_ms zwischendurch einen Überlauf vom (2^24 - 1) auf 0 
gibt, stimmt das Ergebnis trotzdem. Bei den Signed-Typen hast Du dagegen 
undefiniertes Verhalten.

von Walter T. (nicolas)


Lesenswert?

Hans schrieb:
> Wozu denn die vorzeichenbehafteten Datentypen? Die Zeitdifferenz kann
> nicht negativ werden, also kannst Du für alles uint_fast24_t nehmen.

Hallo Hans,
das stimmt - das habe ich in dem Beispiel schlecht dargestellt. Mein 
eigentliches Ziel sieht auch etwas anders aus, und zwar so:
1
volatile uint_least24_t glu_millis = 0; // Millisekundentimer
2
3
/* Timeout-Funktionen */
4
void setTimeout(timeouttimer_t *storage, uint_least24_t value )
5
{
6
    if (storage==NULL) {
7
        error("Null pointer given as storage ");
8
    }
9
    *storage = glu_millis + value;
10
}
11
12
13
bool pollTimeout(timeouttimer_t *storage)
14
{
15
    int_fast24_t diff = *storage - glu_millis;
16
    return (diff < 0);
17
}
und das lädt quasi dazu ein, in der Diskussion tausend 
Nebenkriegsschauplätze zu eröffnen.

Die Vorzeichenrichtige Differenz ist mir durchaus wichtig.

Die Stelle, wo ich als Rückgabewert einen int_fast16_t als 
vorzeichenrichtige Differenz zweier uint24_t benötige ist noch etwas 
länglicher. Da ist der int_fast16_t eine Regelabweichung und ich will 
Multiplikationen mit int32_t oder int24_t dann unbedingt vermeiden. Aber 
wie gesagt: Das ist ein Nebenkriegsschauplatz - hier geht es mir um die 
Differenzbildung.

: Bearbeitet durch User
von Hans (Gast)


Lesenswert?

Ich schätze mal, absolut wasserdicht und platzformunabhängig wirds nicht 
gehen, zumal die 24-Bit-Typen eh nicht Standard sind.

Wenn Du durchgängig signed benutzt, ist der Überlauf der globalen 
Variable undefiniert. Wenn Du von unsigned auf signed zuweist und die 
Zahl nicht in den positiven signed-Wertebereich passt, hast Du ebenfalls 
undefiniertes Verhalten. Falls doch alle Werte reinpassen (z.B. int64_t) 
wird das Ergebnis dagegen immer positiv sein und das spätere Zuweisen an 
einen kleineren Typ (int16_t) undefiniert.

Die sicherste Variante dürfte sein, die Differenz in uint_least24_t zu 
rechnen, einem int_least24_t zuzuweisen und erst dann in int16_t 
umwandeln. Sofern uint_least24_t und int_least24_t die gleiche Breite 
haben, sollte in der Praxis das richtige rauskommen.

von Hans (Gast)


Lesenswert?

Im letzten Absatz sollte es int_fast16_t statt int16_t heißen. Ein 
Problem entsteht ja erst, wenn z.B. der int_fast16_t 32 Bit breit ist, 
der int_least24_t aber nur 24. Mit int16_t kann das nicht passieren.

von Walter T. (nicolas)


Lesenswert?

Hallo Hans,
daß int_fast16_t nie größer als ein uint_fast24_t wird kann man ja 
notfalls mit einem static_assert absichern. Oder besser noch: 
Sicherstellen, daß uint_least24_t und int_least24_t die selbe Größe 
haben:
1
#define static_assert __extension__ _Static_assert
2
3
static_assert( sizeof(int_least24_t)==sizeof(uint_fast24_t),"Integer conversion may fail" );
4
5
6
7
{
8
    uint_least24_t glu_millis = 0;
9
    int_fast16_t diff;
10
11
    diff = (int24_least24_t) (*storage - glu_millis);
12
}
Das wäre doch wahrscheinlich eine Variante, bei der ich vor 
Überraschungen sicher wäre.

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.