I recently wrote some code that uses memcpy
to unpack float
/double
to unsigned integers of the appropriate width, and then uses some bit-shifting to separate the sign bit from the combined significand/exponent.
Note: For my use case, I don't need to separate the latter two parts from eachother, but I do need them in the correct order i.e:
{sign, (exponent, significand)}
, with the latter tuple packed as an unsigned int of sufficient width.
My code is working fine, thoroughly tested and no trouble; however I was a bit alarmed to discover that IEEE-754 doesn't specify the endianness of its binary interchange formats —which to my understanding, means there is a rare possibility that my bit-shifting may be incorrect in the rare occasions where float endianness ≠ integer endianness.
Based on the answered question here, my assumption is that given that bit-shifting is independent of actual endianness in storage, I only need to worry about whether the endianness of my floats matches that of my ints.
I devised the following code loosely following that in the linked answer, but avoiding the use of type-punning through pointer casts, which seems like unspecified/undefined behaviour territory to me:
#include <cstdint>
#include <cstring>
// SAME means "same as integer endianness"
enum class FloatEndian { UNKNOWN, SAME, OPPOSITE, };
FloatEndian endianness() {
float check = -0.0f; // in IEEE-754, this should be all-zero significand and exponent with sign=1
std::uint32_t view;
std::memcpy(&view, &check, sizeof(check));
switch (view) {
case 0x80000000: // sign bit is in most significant byte
return FloatEndian::SAME;
case 0x00000080: // sign bit is in least significant byte
return FloatEndian::OPPOSITE;
default: // can't detect endianness of float
return FloatEndian::UNKNOWN;
}
}
If I ensure that my floats are indeed IEEE-754 with std::numeric_limits<T>::is_iec559
, is my approach a robust and portable way of making sure I get the floats "the right way round" when I chop them up?