Is checking the location of the sign bit enough to determine endianness of IEEE-754 float with respect to integer endianness?

Viewed 105

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 endiannessinteger 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?

1 Answers

Is checking the location of the sign bit enough to determine endianness of IEEE-754 float with respect to integer endianness?

  • As I read it, given the C++ spec and the C spec that it tends to also rely on, checking the sign bit is technically insufficient to determine endian relationship between float/uint32_t. It is likely practically sufficient as endians other than big/little are rare as well as differences between float/uint32_t endian.

  • I would suggest a different constant than -0.0f, maybe -0x1.ca8642p-113f which has the pattern 0x87654321 and would be a more thorough endian test. Quite unclear why OP wants to use a one's-bit-sparse -0.0f to discern 3 possible results.

  • As mentioned by others, in C++, the test should be a compile time one, so thoroughness is not a run-time cost over the simplicity of only testing a sign bit.

  • Relying on is_iec559 is true may unnecessarily limits portability as for that to be true, many non-finite compliance rules are needed. ref. Does your code really need quiet and signaling NANs?

  • See also If is_iec559 is true, does that mean that I can extract exponent and mantissa in a well defined way?.

  • I hope OP also tests that the sizeof(float) == sizeof(uint32_t) else memcpy(&view, &check, sizeof(check)) is bad code.

is my approach a robust and portable way of making sure I get the floats "the right way round" when I chop them up?

  • Code is not as robust and portable as it could be.

  • "when I chop them up" --> that code is not shown, so unanswerable. I am suspect of the endianness() goal that is used to support "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." It is that code that deserves review.

Related