I'm trying to create floating-point NaNs by choosing the fraction bits. But it seems that python float always set the 23rd fraction bit (IEEE754 single) when it interprets a NaN.
So, my question is: is it possible to define a float nan in python without it setting the 23rd bit?
(I'm using Python 2.7)
NaNs in IEEE 754 have this format:
sign = either 0 or 1.
biased exponent = all 1 bits.
fraction = anything except all 0 bits (since all 0 bits represents infinity).
So, a hex representation for a NaN could be 0x7F800001, but when interpreting this int as a float and interpreting it back to int gives 0x7FC00001
1st try: struct.pack/unpack:
import struct
def hex_to_float(value):
return struct.unpack( '@f', struct.pack( '@L', value) )[0]
def float_to_hex(value):
return struct.unpack( '@L', struct.pack( '@f', value) )[0]
print hex(float_to_hex(hex_to_float(0x7f800001)))
# 0x7fc00001
2nd try: ctypes
import ctypes
def float2hex(float_input):
INTP = ctypes.POINTER(ctypes.c_uint)
float_value = ctypes.c_float(float_input)
my_pointer = ctypes.cast(ctypes.addressof(float_value), INTP)
return my_pointer.contents.value
def hex2float(hex_input):
FLOATP = ctypes.POINTER(ctypes.c_float)
int_value = ctypes.c_uint(hex_input)
my_pointer = ctypes.cast(ctypes.addressof(int_value), FLOATP)
return my_pointer.contents.value
print hex(float2hex(hex2float(0x7f800001)))
# 0x7fc00001L
3rd try: xdrlib packers. Same result.
The underlying problem is that you convert a C-float (which has 32bit) to Python-float (which has 64bit, i.e. a double
in C-parlance) and than back to C-float.
The execution of both cconversions after each other doesn't always lead to the original input - you are witnessing such a case.
If the exact bit-pattern is important, you should avoid the above conversions at any cost.
Here are some gory details:
So when struct.unpack('=f', some_bytes)
(please note, that I use the standard size =
-format character as compared to your usage of native size ('@'), for example @L
means different things on Windows and Linux), the following happends:
unpack_float
is called, which calls _PyFloat_Unpack4
, which interprets data (here or here) as a float
,double
(because the function returns a `double') while returning.On x86-64 the last conversion means the the operation VCVTSS2SD
(i.e. Convert Scalar Single-Precision Floating-Point Value to Scalar Double-Precision Floating-Point Value) and this opperation results in
0x7f800001
becomming 0x7ff8000020000000
.
As you see, already the result of the operation struct.unpack( '=f', struct.pack( '=L', value) )[0]
is not what was put in.
However, calling struct.pack(
=f, value)
for a python-float value
(which is a wrapper around C's double
), will get us to _PyFloat_Pack4
, where the conversion from double
to float
happens, i.e. CVTSD2SS
(Convert Scalar Double-Precision Floating-Point Value to Scalar Single-Precision Floating-Point Value) is called and
0x7ff8000020000000
becomes 0x7fc00001
.