Umwandlung von Float in int (bitweise) in C

Lesezeit: 9 Minuten

Benutzeravatar von Anonymous
Anonym

Wie kann angesichts der 32 Bits, die eine IEEE 754-Gleitkommazahl darstellen, die Zahl in eine Ganzzahl konvertiert werden, indem Ganzzahl- oder Bitoperationen für die Darstellung verwendet werden (anstatt einen Maschinenbefehl oder eine Compileroperation zum Konvertieren zu verwenden)?

Ich habe die folgende Funktion, aber sie schlägt in einigen Fällen fehl:

Eingabe: int x (enthält eine 32-Bit-Zahl mit einfacher Genauigkeit im IEEE 754-Format)

  if(x == 0) return x;

  unsigned int signBit = 0;
  unsigned int absX = (unsigned int)x;
  if (x < 0)
  {
      signBit = 0x80000000u;
      absX = (unsigned int)-x;
  }

  unsigned int exponent = 158;
  while ((absX & 0x80000000) == 0)
  {
      exponent--;
      absX <<= 1;
  }

  unsigned int mantissa = absX >> 8;

  unsigned int result = signBit | (exponent << 23) | (mantissa & 0x7fffff);
  printf("\nfor x: %x, result: %x",x,result);
  return result;

  • Dies wirft keinen Float in ein int. Es kopiert nur bitweise ihre Maschinendarstellung, ohne zB zu konvertieren 2.03e1 zu 20 [by rounding] als die (int)2.03e1 gegossen wird.

    – Basile Starynkevitch

    9. September 2012 um 20:59 Uhr


  • Du wollen tun es bitweise? Nun, so machen Sie es bitweise – es interpretiert nur die Bytes neu. Keine Schritte, wirklich.

    – Ry-

    9. September 2012 um 21:00 Uhr

  • Aber 0x7eff8965 = 1325268755 (nach dem Casting). Wenn Sie das HEX in IEEE 754 Calc verwenden, erhalten Sie 1.6983327e+38 und HEX zu Dezimal ergibt: 2130676069 – keines von ihnen gibt das korrekte Ergebnis von 1325268755.

    – Anonym

    9. September 2012 um 21:04 Uhr

  • Dieser Code hat ein undefiniertes Verhalten in C. Siehe Abschnitt 6.5 im Standard.

    – Paul Hankin

    9. September 2012 um 21:30 Uhr

  • Übrigens konvertiert der von Ihnen gepostete Code ein 32-Bit-Signed-Int in sein 32-Bit-IEEE 754-Single-Precision mit Rundung in Richtung Null. Ich weiß es, weil ich es gestern geschrieben habe.

    – Gabe

    10. September 2012 um 4:45 Uhr

C hat die “Vereinigung”, um diese Art von Datenansicht zu handhaben:

typedef union {
  int i;
  float f;
 } u;
 u u1;
 u1.f = 45.6789;
 /* now u1.i refers to the int version of the float */
 printf("%d",u1.i);

  • Dies ist ein undefiniertes Verhalten in jedem mir bekannten C-Standard.

    – TLW

    26. Juli 2016 um 22:14 Uhr

  • @TLW Type punning through union ist seit C99 nicht mehr UB. Dies wird zum Beispiel in N1256 6.5.2.3 Fußnote 82 ausdrücklich erwähnt.

    – Benutzer694733

    13. Februar 2017 um 10:15 Uhr


  • Auch in C99 erlaubt es explizit eine Trap-Darstellung. Und die Verwendung einer Fallendarstellung ist UB. Ich glaube, es wäre für einen Compiler legal, diesen Code bedingungslos zu kompilieren format_hard_drive(); als Ergebnis.

    – TLW

    2. Oktober um 19:33 Uhr

Benutzeravatar von Basile Starynkevitch
Basile Starynkevitch

&x gibt die Adresse von x so hat float* Typ.

(int*)&x wandeln Sie diesen Zeiger in einen Zeiger auf um int dh zu a int* Ding.

*(int*)&x Dereferenzieren Sie diesen Zeiger in ein int Wert. Es wird nicht das tun, was Sie auf Maschinen glauben, wo int und float haben verschiedene Größen.

Und es könnte Endianness-Probleme geben.

Diese Lösung wurde in der verwendet schnelle inverse Quadratwurzel Algorithmus.

  • Sie sagen also, dass der Code nur die Position von x erhält und ausgibt? In diesem Fall würde sich der Wert bei jedem Durchlauf ändern.

    – Anonym

    9. September 2012 um 21:09 Uhr

  • Nein, es gibt die Ganzzahl an, die an der Stelle des Floats enthalten ist, also wann sizeof(int) == sizeof[float] es gibt die int derselben Maschinenbitdarstellung wie Ihre x ; nichts wird gedruckt, es sei denn, Sie rufen eine Druckroutine wie auf printf (was nicht in deiner Frage steht)

    – Basile Starynkevitch

    9. September 2012 um 21:18 Uhr


  • Ok, es gibt also den an der Stelle im Speicher gespeicherten Wert und wandelt ihn in einen int-Typ um. Wie kann ich das ohne Casting machen?

    – Anonym

    9. September 2012 um 21:27 Uhr

  • @BasileStarynkevitch: Was wäre das Problem mit Endianness? Wenn Sie nur die Bits eines Floats auswählen möchten, ist es meiner Meinung nach egal, ob ints Big- oder Little-Endian gespeichert werden.

    – Björn Lindqvist

    21. Januar 2018 um 16:00 Uhr

  • Endianness wäre ein Problem, wenn Sie ein Float in ein unsigned int konvertieren würden, wobei Sie die Bits als Flags verwenden und die sendende Funktion/Programm/Gerät nur Floats senden kann.

    – Mark Walsh

    15. Januar 2019 um 20:37 Uhr

Benutzeravatar von Eric Postpischil
Eric Postpischil

(Jemand sollte diese Antwort noch einmal überprüfen, insbesondere Grenzfälle und das Runden negativer Werte. Außerdem habe ich sie für Runden auf den nächsten Wert geschrieben. Um die Konvertierung von C zu reproduzieren, sollte dies in Runden auf Null geändert werden.)

Im Wesentlichen ist der Prozess:

Trennen Sie die 32 Bits in ein Vorzeichenbit (s), acht Exponentenbits (e) und 23 signifikante Bits (f). Wir behandeln diese als ganze Zahlen im Zweierkomplement.

Wenn e 255 ist, ist das Gleitkommaobjekt entweder unendlich (if f Null ist) oder ein NaN (ansonsten). In diesem Fall kann die Konvertierung nicht durchgeführt werden und es sollte ein Fehler gemeldet werden.

Ansonsten, wenn e nicht null ist, addiere 224 zu f. (Wenn e nicht Null ist, hat die Mantisse implizit ein 1-Bit am Anfang. Hinzufügen 224 macht das etwas explizit in f.)

Subtrahiere 127 von e. (Dies wandelt den Exponenten von seiner voreingenommenen/kodierten Form in den tatsächlichen Exponenten um. Wenn wir eine allgemeine Umwandlung in einen beliebigen Wert durchführen würden, müssten wir den Sonderfall „wann“ behandeln e ist Null: Subtrahieren Sie 126 statt 127. Da wir aber nur in ein ganzzahliges Ergebnis konvertieren, können wir diesen Fall vernachlässigen, solange das ganzzahlige Ergebnis für diese winzigen Eingabezahlen Null ist.)

Wenn s 0 ist (das Vorzeichen ist positiv) und e 31 oder mehr ist, dann überläuft der Wert eine vorzeichenbehaftete 32-Bit-Ganzzahl (es ist 231 oder größer). Die Konvertierung kann nicht durchgeführt werden und es sollte ein Fehler gemeldet werden.

Wenn s ist 1 (das Vorzeichen ist negativ) und e größer als 31 ist, dann überläuft der Wert eine vorzeichenbehaftete 32-Bit-Ganzzahl (er ist kleiner oder gleich -232). Wenn s ist ein, e ist 32, und f ist größer als 224 (eines der ursprünglichen signifikanten Bits wurde gesetzt), dann überläuft der Wert eine vorzeichenbehaftete 32-Bit-Ganzzahl (er ist kleiner als -231; wenn das Original f Null wären, wäre es genau -231, die nicht überläuft). In jedem dieser Fälle kann die Konvertierung nicht durchgeführt werden und es sollte ein Fehler gemeldet werden.

Jetzt haben wir eine sein eund ein f für einen Wert, der nicht überläuft, damit wir den endgültigen Wert vorbereiten können.

Wenn s ist 1, gesetzt f zu –f.

Der Exponentenwert liegt für eine Mantisse zwischen 1 (einschließlich) und 2 (ausschließlich), aber unsere Mantisse beginnt mit einem Bit bei 224. Darauf müssen wir uns also einstellen. Wenn e ist 24, unser Signifikand ist korrekt, und wir sind fertig, also kehren Sie zurück f als Ergebnis. Wenn e größer als 24 oder kleiner als 24 ist, müssen wir die Signifikand entsprechend verschieben. Auch wenn wir umziehen f Richtig, wir müssen es möglicherweise runden, um ein Ergebnis zu erhalten, das auf die nächste ganze Zahl gerundet wird.

Wenn e größer als 24 ist, Verschiebung f links e-24 Bit. Zurückkehren f als Ergebnis.

Wenn e kleiner als -1 ist, liegt die Gleitkommazahl zwischen -½ und ½ (ausschließlich). Geben Sie 0 als Ergebnis zurück.

Sonst verschieben wir uns f rechts 24-e Bits. Wir werden jedoch zuerst die Bits speichern, die wir zum Runden benötigen. Satz r zum Ergebnis der Umwandlung von f in eine vorzeichenlose 32-Bit-Ganzzahl und Verschiebung nach links um 32-(24-e) Bits (äquivalent links von 8+e Bits). Dies nimmt die Bits, aus denen herausgeschoben wird f (unten) und sie in den 32 Bits „links anpasst“, sodass wir eine feste Position haben, an der sie beginnen.

Wechsel f rechts 24-e Bits.

Wenn r ist kleiner als 231, tue nichts (dies ist ein Abrunden; die Verschiebung hat abgeschnittene Bits). Wenn r ist größer als 231füge eins hinzu f (das ist aufrunden). Wenn r gleich 231fügen Sie das niedrige Bit von hinzu f zu f. (Wenn f ungerade ist, fügen Sie eins hinzu f. Von den beiden gleich nahen Werten wird auf den geraden Wert gerundet.) Return f.

  • Danke für die Erklärung. Ich habe die Funktion geschrieben, aber sie schlägt in einigen Fällen fehl.

    – Anonym

    10. September 2012 um 0:59 Uhr

  • “Subtrahiere 127 von e.” passiert wann e > 0. Sonst “Subtrahiere 126 von 0.”

    – chux – Wiedereinsetzung von Monica

    2. Dezember 2013 um 1:07 Uhr

  • @chux: Ja, man müsste sich bei der Konvertierung einer Gleitkommacodierung in eine Zahl im Allgemeinen anpassen. Diese Frage fragt nach dem Sonderfall der Konvertierung einer Gleitkommacodierung in eine Ganzzahl. In diesem Fall können wir den richtigen Umgang mit winzigen Werten vernachlässigen, da sie am Ende Null ergeben.

    – Eric Postpischil

    2. Dezember 2013 um 14:18 Uhr

// With the proviso that your compiler implementation uses
// the same number of bytes for an int as for a float:
// example float
float f = 1.234f;
// get address of float, cast as pointer to int, reference
int i = *((int *)&f);
// get address of int, cast as pointer to float, reference
float g = *((float *)&i);
printf("%f %f %08x\n",f,g,i);

Benutzeravatar von wildplasser
Wildpässer

float x = 43.133;
int y;

assert (sizeof x == sizeof y);
memcpy (&y, &x, sizeof x);
...

  • memcpy hat nicht funktioniert. int x (enthält 32-Bit-Float) ist die Eingabe, dann int Ergebnis; memcpy(&result, &x, 4) funktioniert nicht. (4 ist ok, da es nur auf 32-Bit-Rechnern läuft)

    – Anonym

    9. September 2012 um 21:37 Uhr

  • Vielleicht ist Ihr Assert (oder Ihre Größe) kaputt? Übrigens: Hoppla, ich hätte x statt f verwenden sollen. BRB.

    – Wildpässer

    9. September 2012 um 21:53 Uhr

Sie können den Schwimmer mit einer Referenz werfen. Ein Cast wie dieser sollte niemals Code generieren.

C++

float f = 1.0f;
int i = (int &)f;
printf("Float %f is 0x%08x\n", f, i);

Ausgabe:

Float 1.000000 is 0x3f800000

Wenn Sie einen Cast im C++-Stil wünschen, verwenden Sie einen reinterpret_cast wie diesen.

int i = reinterpret_cast<int &>(f);

Es funktioniert nicht mit Ausdrücken, Sie müssen es in einer Variablen speichern.

    int i_times_two;
    float f_times_two = f * 2.0f;
    i_times_two = (int &)f_times_two;

    i_times_two = (int &)(f * 2.0f);
main.cpp:25:13: error: C-style cast from rvalue to reference type 'int &'

  • memcpy hat nicht funktioniert. int x (enthält 32-Bit-Float) ist die Eingabe, dann int Ergebnis; memcpy(&result, &x, 4) funktioniert nicht. (4 ist ok, da es nur auf 32-Bit-Rechnern läuft)

    – Anonym

    9. September 2012 um 21:37 Uhr

  • Vielleicht ist Ihr Assert (oder Ihre Größe) kaputt? Übrigens: Hoppla, ich hätte x statt f verwenden sollen. BRB.

    – Wildpässer

    9. September 2012 um 21:53 Uhr

Benutzeravatar von Alex Brown
Alex Braun

Sie können eine Fließkommazahl nicht (sinnvoll) in eine ‘Ganzzahl’ umwandeln (signed int oder int) auf diese Weise.

Es kann am Ende den Integer-Typ haben, aber es ist eigentlich nur ein Index in den Codierungsraum von IEEE754, kein sinnvoller Wert an sich.

Sie könnten argumentieren, dass ein unsigned int dient einem doppelten Zweck als Bitmuster und als ganzzahliger Wert, aber int nicht.


Es gibt auch Plattformprobleme mit Bit-Manipulation von signierten Ints.

  • Sinnvoller Gebrauch: Erhalt von zwei int16_ts in einem Bus, die eigentlich einen darstellen float32. Interpretiere die beiden neu int16_t als Schwimmer.

    – Gauthier

    30. März 2015 um 10:46 Uhr

1433260cookie-checkUmwandlung von Float in int (bitweise) in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy