-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
struct (un)packing of half-precision nan
floats is non-invertible
#130317
Comments
It seems you are on IEEE-platform, or unpacking special values will fail for float and double formats. So for those formats, pack/unpack functions work by copying bits. But not PyFloat_Pack2() and PyFloat_Unpack2(). E.g. the later just ignores all payload in the nan value and maps Lines 2402 to 2405 in 12e1d30
The PyFloat_Pack2() also ignores all payload from double nan: Lines 2050 to 2059 in 12e1d30
Looks as a bug for me. CC @mdickinson Edit: assuming doubles are binary64, following patch fix your tests: diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 3b72a1e7c3..e473fb72fe 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -2048,14 +2048,16 @@ PyFloat_Pack2(double x, char *data, int le)
bits = 0;
}
else if (isnan(x)) {
- /* There are 2046 distinct half-precision NaNs (1022 signaling and
- 1024 quiet), but there are only two quiet NaNs that don't arise by
- quieting a signaling NaN; we get those by setting the topmost bit
- of the fraction field and clearing all other fraction bits. We
- choose the one with the appropriate sign. */
sign = (copysign(1.0, x) == -1.0);
e = 0x1f;
- bits = 512;
+
+ uint64_t v;
+
+ memcpy(&v, &x, sizeof(v));
+ bits = v & 0x1ff;
+ if (v & 0x800000000000) {
+ bits += 0x200;
+ }
}
else {
sign = (x < 0.0);
@@ -2401,7 +2403,16 @@ PyFloat_Unpack2(const char *data, int le)
}
else {
/* NaN */
- return sign ? -fabs(Py_NAN) : fabs(Py_NAN);
+ uint64_t v = ((sign? 0xff00000000000000 : 0x7f00000000000000)
+ + 0xf0000000000000);
+
+ if (f & 0x200) {
+ v += 0x800000000000;
+ f -= 0x200;
+ }
+ v += f;
+ memcpy(&x, &v, sizeof(v));
+ return x;
}
}
FYI: #55943. Probably the reason why payload was ignored is that the patch was adapted from numpy sources. |
@tim-one, does it looks as an issue for you? |
Bug report
Bug description:
I noticed that chaining
struct.unpack()
andstruct.pack()
for IEEE 754 Half Precision floats (e
) is non-invertible fornan
. E.g.:IEEE
nan
s aren't unique, so this isn't that surprising... However I found it curious that the same behavior is not exhibited forfloat
(f
) ordouble
(d
) format, where every original bit pattern I tested could be recovered from the unpackednan
object.Is this by design?
Here's a quick
pytest
script that tests over a broad range ofnan
/inf
/-inf
cases for each encoding format.CPython versions tested on:
3.13, 3.11, 3.12
Operating systems tested on:
Linux, Windows
The text was updated successfully, but these errors were encountered: