I was working on something and saw this oddity, thought I would share it.

$ ./a.out
in.val = 86.3115158 -> Its a valid floating point number
out1.val = nan -> Swapped it and now its a NaN! That's OK swap it back.
out2.val = 86.4365158 -> What, its not the same number???
in.m = {0x7f, 0x9f, 0xac, 0x42} -> Notice the second number is 0x9f
out1.m = {0x42, 0xac, 0xdf, 0x7f} -> But in the flipped version it became 0xdf?
out2.m = {0x7f, 0xdf, 0xac, 0x42} -> So, this can't be the same number.

The error comes from when I swaped the first number 86.3115158, its flipped version produced a NaN.
That would be OK if the flipped version just returned the same number, but that is not the case!
I surmised that when I assign the NaN value to another float, the 'a' bit in the register was
changed to indicate a Quiet NaN http://en.wikipedia.org/wiki/NaN#Floating_point

So, I changed the byteSwap function to not return the result but just modify the input.
in.val = 86.3115158 -> Its a valid float.
in.val = nan -> Swapped it and now its a NaN!
in.val = 86.3115158 -> Swap it back and its OK!

Because I don't do an assignment to a float it works.
$

Here is the code
#include <string>
#include <iostream>
#include <iomanip>

//-------------------------------------------------------------------------
// Swap stuff
template <class T> inline void byteSwap2(T& v) {
    union {
      T val;
      char m[sizeof(T)];
    } in, tmp;
    in.val = v;
    for (unsigned int j(1); j <= sizeof(T); j++) {
      tmp.m[j-1] = in.m[sizeof(T)-j];
    }
    memcpy(&v,tmp.m,sizeof(T));
    return;
}

//-------------------------------------------------------------------------
// Swap stuff
template <class T> inline T byteSwap(const T& v) {
   unsigned int j(1);
   union {
      T val;
      char m[sizeof(T)];
   } in, tmp;
   in.val = v;
   //----------------------------------------------------------------------
   // Swap the data
   for (; j <= sizeof(T); j++)
      tmp.m[j-1] = in.m[sizeof(T)-j];
   return (tmp.val);
}
//-------------------------------------------------------------------------
// Print stuff
void printIt(const char *name,char o[],int s){
   unsigned int j(0);
   std::cout << name << "   = {";
   for (; j<s-1; j++)
      std::cout << std::hex << std::showbase << int(o[j]&0xff) << ", ";
   std::cout << int(o[j]&0xff) << "}"; 
}
int main(){
   unsigned int j(0);
   union {
      float val;
      char m[sizeof(float)];
   } in,out1,out2;

   //----------------------------------------------------------------------
   // I need a specific value for the floating point number, so in order
   // to get it exact I load it like this
   in.m[0] = 0x7f, in.m[1] = 0x9f, in.m[2] = 0xac, in.m[3] = 0x42;

   //----------------------------------------------------------------------
   // in.val is a valid floating point number
   std::cout << std::setprecision(9) << "in.val   = " << in.val;
   std::cout << "                -> Its a valid floating point number" << std::endl;
   //----------------------------------------------------------------------
   // Swap the number
   out1.val = byteSwap(in.val);
   //----------------------------------------------------------------------
   // Print out result, its a NaN?
   std::cout << "out1.val = " << out1.val;
   std::cout << "                       -> Swapped it and now its a NaN!  That's OK swap it back." << std::endl;
   //----------------------------------------------------------------------
   // That OK, swap it back
   out2.val = byteSwap(out1.val);
   std::cout << "out2.val = " << out2.val;
   std::cout << "                -> What, its not the same number???" << std::endl;

   printIt("in.m  ", in.m,sizeof(out1.m));
   std::cout << "  -> Notice the second number is 0x9f" << std::endl;
   printIt("out1.m", out1.m,sizeof(out1.m));
   std::cout << "  -> But in the flipped version it became 0xdf?" << std::endl;
   printIt("out2.m", out2.m,sizeof(out1.m));
   std::cout << "  -> So, this can't be the same number." << std::endl;

   std::cout << "\nThe error comes from when I swaped the first number " 
             << in.val << ", its flipped version produced a NaN.\n" 
             << "That would be OK if the flipped version just returned the"
             << " same number, but that is not the case!\n" 
             << "I surmised that when I assign the NaN value to another "
             << "float, the 'a' bit in the register was\n"
             << "changed to indicate a Quiet NaN http://en.wikipedia.org/wiki/NaN#Floating_point\n\n"
             << "\nSo, I changed the byteSwap function to not return the " 
             << "result but just modify the input." << std::endl;

   //----------------------------------------------------------------------
   // Print out valid floating point number
   std::cout << "in.val   = " << in.val;
   std::cout << "                -> Its a valid float." << std::endl;
   //----------------------------------------------------------------------
   // Swap the number
   byteSwap2(in.val);
   //----------------------------------------------------------------------
   // Print out result, its a NaN?
   std::cout << "in.val   = " << in.val;
   std::cout << "                       -> Swapped it and now its a NaN!" << std::endl;
   //----------------------------------------------------------------------
   // Swap the number
   byteSwap2(in.val);
   //----------------------------------------------------------------------
   // Print out result, its correct
   std::cout << "in.val   = " << in.val;
   std::cout << "                -> Swap it back and its OK!" << std::endl;

   std::cout << "\nBecause I don't do an assignment to a float it works." << std::endl;

   return 0; 
}

Dani AI

Generated

Nice catch — reversing those bytes produced an IEEE‑754 NaN bit pattern (all exponent bits = 1 and a non‑zero fraction). On IEEE formats the most‑significant fraction bit is the “quiet” bit: setting it makes a quiet NaN (qNaN) instead of a signaling NaN (sNaN), so a single fraction‑bit flip explains the 0x9f → 0xdf change you saw. (gnu.org)

Why the return-by-value and the in-place versions behave differently: when you return/assign a floating value the value usually moves through the FP ABI (registers or FP unit) and compilers or the hardware may “quiet” or canonicalize NaNs during conversion/propagation. In short, the platform/ABI and compiler decide how NaN payloads and the quiet bit propagate, so the behavior is implementation-dependent (and differs for x87 vs SSE, and across compilers). That matches ’s intuition — it’s not purely your C code, it’s the FP conventions in use. (llvm.googlesource.com)

Practical, portable fixes (what to do now)

  • Don’t rely on reading inactive union members for bit fiddling in C++ (that is not portable). Use well‑defined bit casts or memcpy. (en.cppreference.com)
  • Do the byte swap on the integer representation and write the exact bits back into the float’s storage (not via FP math or implicit FP returns). Example pattern (C++20 or later):
#include <bit>
#include <cstdint>
#include <cstring>

void byteswap_inplace(float &f) {
    uint32_t u = std::bit_cast<uint32_t>(f);
    u = __builtin_bswap32(u);        // or std::byteswap / portable routine
    std::memcpy(&f, &u, sizeof u);   // preserve exact bit pattern in memory
}

If you cannot use C++20, memcpy to/from a uint32_t does the same job. Returning an integer bit pattern (or writing the swapped bits into caller memory) avoids ABI-driven “quieting.” For deterministic behavior across compilers/platforms prefer integer/byte operations + memcpy or std::bit_cast; avoid union type‑punning for portability. (en.cppreference.com)

Summary: the observed NaN/byte change is expected under IEEE/implementation rules; preserve raw bits by swapping integers and copying to memory (or use an in‑place memcpy approach as you discovered).

Recommended Answers

All 2 Replies

With what compiler did you get this? I don't think this is necessarily standard-compliant behavior.

gcc version 4.1.2 20080704

vendor_id       : GenuineIntel
model name      : Intel(R) Xeon(R) CPU   E5645  @ 2.40GHz
cpu MHz         : 2400.265
cache size      : 12288 KB

Would it be a function of the compiler or hardware? I was guessing that when the floating point register gets loaded with the NaN, the register changed that bit to indicate quite NaN.
Thoughts?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.