Analysieren Sie IEEE-Gleitkommazahlen mit doppelter Genauigkeit auf einem C-Compiler ohne Typ mit doppelter Genauigkeit

Analysieren Sie IEEE-Gleitkommazahlen mit doppelter Genauigkeit auf einem C-Compiler ohne Typ mit doppelter Genauigkeit


Ich arbeite mit einem 8-Bit-AVR-Chip. Es gibt keinen Datentyp für ein 64-Bit-Double (Double wird nur dem 32-Bit-Float zugeordnet). Ich werde jedoch 64-Bit-Doubles über Serial empfangen und muss 64-Bit-Doubles über Serial ausgeben.


Wie kann ich das 64-Bit-Double ohne Casting in ein 32-Bit-Float und wieder zurück konvertieren? Das Format sowohl für 32-Bit als auch für 64-Bit folgt IEEE 754. Natürlich gehe ich von einem Genauigkeitsverlust bei der Konvertierung in 32-Bit-Float aus.


Für die Konvertierung von 64-Bit in 32-Bit-Float probiere ich Folgendes aus:


// Script originally from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1281990303
float convert(uint8_t *in) {
union {
float real;
uint8_t base[4];
} u;
uint16_t expd = ((in[7] & 127) << 4) + ((in[6] & 240) >> 4);
uint16_t expf = expd ? (expd - 1024) + 128 : 0;
u.base[3] = (in[7] & 128) + (expf >> 1);
u.base[2] = ((expf & 1) << 7) + ((in[6] & 15) << 3) + ((in[5] & 0xe0) >> 5);
u.base[1] = ((in[5] & 0x1f) << 3) + ((in[4] & 0xe0) >> 5);
u.base[0] = ((in[4] & 0x1f) << 3) + ((in[3] & 0xe0) >> 5);
return u.real;
}

Für Zahlen wie 1.0 und 2.0 funktioniert das obige, aber als ich getestet habe, dass ich 1.1 als 64-Bit-Double übergeben habe, war die Ausgabe etwas daneben (buchstäblich kein Wortspiel!), obwohl dies ein Problem sein könnte mit meine Prüfung. Siehe:


// Comparison of bits for a float in Java and the bits for a float in C after
// converted from a 64-bit double. Last bit is different.
// Java code can be found at https://gist.github.com/912636
JAVA FLOAT: 00111111 10001100 11001100 11001101
C CONVERTED FLOAT: 00111111 10001100 11001100 11001100

Antworten:


IEEE gibt fünf verschiedene Rundungsmodi an, aber der standardmäßig zu verwendende ist Round half to even. Sie haben also eine Mantisse der Form 10001100 11001100 11001100 11001100 ... und Sie müssen sie auf 24 Bit runden. Nummeriert man die Bits von 0 (höchstwertig), ist Bit 24 1; aber das reicht nicht aus, um Ihnen zu sagen, ob Sie Bit 23 aufrunden sollen oder nicht. Wenn alle verbleibenden Bits 0 wären, würden Sie nicht aufrunden, da Bit 23 0 (gerade) ist. Aber die restlichen Bits sind nicht Null, also runden Sie in allen Fällen auf.


Einige Beispiele:


10001100 11001100 11001100 10000000 ... (alle Nullen) wird nicht aufgerundet, da Bit 23 bereits gerade ist.


10001100 11001100 11001101 10000000 ... (alle Nullen) rundet auf, weil Bit 23 ungerade ist.


10001100 11001100 1100110x 10000000...0001 wird immer aufgerundet, da die restlichen Bits nicht alle Null sind.


10001100 11001100 1100110x 0xxxxxxx... wird nie aufgerundet, da Bit 24 Null ist.


Einige Code-Antworten


// Script originally from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1281990303 float convert(uint8_t *in) {   union {
float real;
uint8_t base[4];
} u;
uint16_t expd = ((in[7] &
127) <<
4) + ((in[6] &
240) >>
4);
uint16_t expf = expd ? (expd - 1024) + 128 : 0;
u.base[3] = (in[7] &
128) + (expf >>
1);
u.base[2] = ((expf &
1) <<
7) + ((in[6] &
15) <<
3) + ((in[5] &
0xe0) >>
5);
u.base[1] = ((in[5] &
0x1f) <<
3) + ((in[4] &
0xe0) >>
5);
u.base[0] = ((in[4] &
0x1f) <<
3) + ((in[3] &
0xe0) >>
5);
return u.real;
}
// Comparison of bits for a float in Java and the bits for a float in C after // converted from a 64-bit double. Last bit is different. // Java code can be found at https://gist.github.com/912636 JAVA FLOAT:
00111111 10001100 11001100 11001101 C CONVERTED FLOAT: 00111111 10001100 11001100 11001100
double extend_float(float f) {
unsigned char flt_bits[sizeof(float)];
unsigned char dbl_bits[sizeof(double)] = {0};
unsigned char sign_bit;
unsigned char exponent;
unsigned int significand;
double out;
memcpy(&flt_bits[0], &f, sizeof(flt_bits));
/// printf("---------------------------------------\n");
/// printf("float = %f\n", f);
#if LITTLE_ENDIAN
reverse_bytes(flt_bits, sizeof(flt_bits));
#endif
/// dump_bits(&flt_bits[0], sizeof(flt_bits));
/* IEEE 754 single precision
* 1 sign bit flt_bits[0] &
0x80
* 8 exponent bits
flt_bits[0] &
0x7F | flt_bits[1] &
0x80
* 23 fractional bits
flt_bits[1] &
0x7F | flt_bits[2] &
0xFF |
* flt_bits[3] &
0xFF
*
* E = 0 &
F = 0 ->
+/- zero
* E = 0 &
F != 0 ->
sub-normal
* E = 127 &
F = 0 ->
+/- INF
* E = 127 &
F != 0 ->
NaN
*/
sign_bit = (flt_bits[0] &
0x80) >>
7;
exponent = ((flt_bits[0] &
0x7F) <<
1) | ((flt_bits[1] &
0x80) >>
7);
significand = (((flt_bits[1] &
0x7F) <<
16) |
(flt_bits[2] <<
8) |
(flt_bits[3]));
/* IEEE 754 double precision
* 1 sign bit dbl_bits[0] &
0x80
* 11 exponent bits
dbl_bits[0] &
0x7F | dbl_bits[1] &
0xF0
* 52 fractional bits
dbl_bits[1] &
0x0F | dbl_bits[2] &
0xFF
* dbl_bits[3] &
0xFF | dbl_bits[4] &
0xFF
* dbl_bits[5] &
0xFF | dbl_bits[6] &
0xFF
* dbl_bits[7] &
0xFF
*
* E = 0 &
F = 0 ->
+/- zero
* E = 0 &
F != 0 ->
sub-normal
* E = x7FF &
F = 0 ->
+/- INF
* E = x7FF &
F != 0 ->
NaN
*/
dbl_bits[0] = flt_bits[0] &
0x80;
/* pass the sign bit along */
if (exponent == 0) {
if (significand == 0) { /* +/- zero */ /* nothing left to do for the outgoing double */
} else { /* sub-normal number */ /* not sure ... pass on the significand?? */
}
} else if (exponent == 0xFF) { /* +/-INF and NaN */
dbl_bits[0] |= 0x7F;
dbl_bits[1] = 0xF0;
/* pass on the significand */
} else { /* normal number */
signed int int_exp = exponent;
int_exp -= 127;
/* IEEE754 single precision exponent bias */
int_exp += 1023;
/* IEEE754 double precision exponent bias */
dbl_bits[0] |= (int_exp &
0x7F0) >>
4;
/* 7 bits */
dbl_bits[1] = (int_exp &
0x00F) <<
4;
/* 4 bits */
}
if (significand != 0) {
/* pass on the significand most-significant-bit first */
dbl_bits[1] |= (flt_bits[1] &
0x78) >>
3;
/* 4 bits */
dbl_bits[2] = (((flt_bits[1] &
0x07) <<
5) | /* 3 bits */((flt_bits[2] &
0xF8) >>
3));
/* 5 bits */
dbl_bits[3] = (((flt_bits[2] &
0x07) <<
5) | /* 3 bits */((flt_bits[3] &
0xF8) >>
3));
/* 5 bits */
dbl_bits[4] = ((flt_bits[3] &
0x07) <<
5);
/* 3 bits */
}
///dump_bits(&dbl_bits[0], sizeof(dbl_bits));
#if LITTLE_ENDIAN
reverse_bytes(&dbl_bits[0], sizeof(dbl_bits));
#endif
memcpy(&out, &dbl_bits[0], sizeof(out));
return out;
}