27

Is it possible to convert floats from big to little endian? I have a big endian value from a PowerPC platform that I am sendING via TCP to a Windows process (little endian). This value is a float, but when I memcpy the value into a Win32 float type and then call _byteswap_ulongon that value, I always get 0.0000?

What am I doing wrong?

5
  • take a look at that question: stackoverflow.com/questions/1786137/… Commented May 6, 2010 at 16:34
  • I think they're both IEEE format, but you should double check. Commented May 6, 2010 at 16:42
  • What happens, if you don't call _byteswap_ulong ? Commented Nov 2, 2010 at 18:56
  • <strike>Do not implement such things. Checkout boost::endian at: boost.org/doc/libs/1_64_0/libs/endian/doc/index.html</strike> Commented May 18, 2017 at 1:50
  • @morteza's answer, that he tried but failed to strikeout, doesn't work -- float support was removed from boost::endian because it didn't work. Commented Aug 25, 2018 at 5:59

11 Answers 11

45

simply reverse the four bytes works

float ReverseFloat( const float inFloat ) { float retVal; char *floatToConvert = ( char* ) & inFloat; char *returnFloat = ( char* ) & retVal; // swap the bytes into a temporary buffer returnFloat[0] = floatToConvert[3]; returnFloat[1] = floatToConvert[2]; returnFloat[2] = floatToConvert[1]; returnFloat[3] = floatToConvert[0]; return retVal; } 
Sign up to request clarification or add additional context in comments.

12 Comments

That is perfectly legal C code and no compiler should break it. I have tested it with VC6, Visual Studio 2008, Visual Studio 2010 and c++ Builder 2010. None of those compilers break this code.
@Tomek: This does not violate the strict aliasing rule. Both C and C++ explicitly permit any type of object to be accessed as an array of char (and consequently, through a char*). The "cast through a union" hack from the link you posted results in undefined behavior (reading from a member of a union other than the last one written to results in undefined behavior).
@Tomek: It doesn't matter whether it is bad to you; it matters whether its behavior is well-defined. Reinterpretation through a char* is well-defined (cf. C++03 §3.10/15). The union hack is not (cf. C++03 §9.5/1). If you are familiar with the language, then avoiding undefined or implementation defined behavior is not particularly difficult.
I have another little problem with this code. Despite the name of the function, it does not behave at all like say ntohl(). If the endianess is already right it should do nothing. Here, it swap bytes anyway.
@Tomek: So you prefer a solution which invokes UB to one which is perfectly well defined? That... makes no sense at all. You say "the code is legal but it doesn't mean it works.", and then, when talking about using a union... "(AFAIR it is dreaded UB) but it seems to work.". I don't think I've ever heard a more inconsistent argument. And how can you say "if you want to fiddle with bits at such low level you are going to hit UB or IDB sooner or later."? What are you talking about? That is completely untrue, you just need to know what you are doing.
|
12

Here is a function can reverse byte order of any type.

template <typename T> T bswap(T val) { T retVal; char *pVal = (char*) &val; char *pRetVal = (char*)&retVal; int size = sizeof(T); for(int i=0; i<size; i++) { pRetVal[size-1-i] = pVal[i]; } return retVal; } 

Comments

9

I found something roughly like this a long time ago. It was good for a laugh, but ingest at your own peril. I've not even compiled it:

void * endian_swap(void * arg) { unsigned int n = *((int*)arg); n = ((n >> 8) & 0x00ff00ff) | ((n << 8) & 0xff00ff00); n = ((n >> 16) & 0x0000ffff) | ((n << 16) & 0xffff0000); *arg = n; return arg; } 

9 Comments

Heh heh. Knew that would get downvoted. Only posted it for the giggle. When I interviewed at EA many, many years ago, they had a version that did the shifts in 1, 2, 4, 8, then 16 bit widths and asked what it did.
First off, I think it was pretty clear that this was nothing more than a little entertainment. Second, I'm not sure that the aliasing rules apply. While there is the possibility that arg could point to memory that will be owned by n, the optimizer would not find that relevant, since it never uses n again after assigning to *arg. Aliasing rules don't come into effect until you do something like: a=5;*b=7;c=a+(*b);, where the value of c cannot be computed from a cached value of a because the assignment to *b may have affected it. Though I should have said *((int)arg) = n; =]
Well, if performance would be important, this version would be preferred over the one doing byte shuffling. Byte shuffling will exhibit partial stall once you read the value back as dword. In this case you always stay in dword size, and it can be pipelined well, with no stalls. I am not surprised EA showed it, as game developers use functions like this for time critical code.
*arg = n;? Is assigning to a dereferenced void pointer going to work without casting? I would expect the compiler to complain.
@blubberdiblub every compiler I've tried errors out when trying to do that, e.g. gcc says "error: invalid use of void expression". it really needs to be *(unsigned int*)arg = n; not sure how OP ever expects the original to work! godbolt.org/z/76fq1r4Gr has a MWE
|
9

An elegant way to do the byte exchange is to use a union:

float big2little (float f) { union { float f; char b[4]; } src, dst; src.f = f; dst.b[3] = src.b[0]; dst.b[2] = src.b[1]; dst.b[1] = src.b[2]; dst.b[0] = src.b[3]; return dst.f; } 

Following jjmerelo's recommendation to write a loop, a more generic solution could be:

typedef float number_t; #define NUMBER_SIZE sizeof(number_t) number_t big2little (number_t n) { union { number_t n; char b[NUMBER_SIZE]; } src, dst; src.n = n; for (size_t i=0; i<NUMBER_SIZE; i++) dst.b[i] = src.b[NUMBER_SIZE-1 - i]; return dst.n; } 

6 Comments

Shouldn't you put the assignment dst.b <-> src.b into a loop?
Indeed, it is a possible solution to write a loop. First I thought so to make the code more extensible to other sizes (for example, to convert doubles). And the compiler, when optimizing, serializes the loop by generating the same code as without a loop. The reason for putting the four assignments without a loop has been for simplicity, because perhaps it is better understood. The solution with a loop would be: for (unsigned i=0; i<sizeof(ffoat); i++) dst.b[i] = src.b[sizeof(ffoat)-1 - i];
This is undefined behaviour in C++. It's not valid to read a variable that wasn't assigned in a union. It may work well in most compilers - but then so does crossing the road without looking.
Thank you very much for your comment. After 30 years of programming in C I have learned this aspect about unions. When reading your comment I thought it would be a feature introduced in C++, but I searched the first edition of Kernighan-Ritchie and it was already there: it's safe to read a member of a union if it's the last one in which you have written, but the behavior remains undefined otherwise.
reversing the bytes of a floating point number does not necessarily produce another valid floating point number.
|
3

Don't memcpy the data directly into a float type. Keep it as char data, swap the bytes and then treat it as a float.

Comments

2

From SDL_endian.h with slight changes:

std::uint32_t Swap32(std::uint32_t x) { return static_cast<std::uint32_t>((x << 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x >> 24)); } float SwapFloat(float x) { union { float f; std::uint32_t ui32; } swapper; swapper.f = x; swapper.ui32 = Swap32(swapper.ui32); return swapper.f; } 

1 Comment

reversing the bytes of a floating point number does not necessarily produce another valid floating point number.
1

It might be easier to use the ntoa and related functions to convert from network to host and from host to network..the advantage it would be portable. Here is a link to an article that explains how to do this.

Comments

0

This value is a float, but when I "memcpy" the value into a win32 float type and then call _byteswap_ulong on that value, I always get 0.0000?

This should work. Can you post the code you have?

However, if you care for performance (perhaps you do not, in that case you can ignore the rest), it should be possible to avoid memcpy, either by directly loading it into the target location and swapping the bytes there, or using a swap which does the swapping while copying.

Comments

0

in some case, especially on modbus: network byte order for a float is:

nfloat[0] = float[1] nfloat[1] = float[0] nfloat[2] = float[3] nfloat[3] = float[2] 

1 Comment

reversing the bytes of a floating point number does not necessarily produce another valid floating point number.
0

Boost libraries have already been mentioned by @morteza and @AnotherParker, stating that the support for float was removed. However, it was added back in a subset of the library since they wrote their comments.

Using Boost.Endian conversion functions, version 1.77.0 as I wrote this answer, you can do the following:

float input = /* some value */; float reversed = input; boost::endian::endian_reverse_inplace(reversed); 

Check the FAQ to learn why the support was removed then partially added back (mainly, because a reversed float may not be valid anymore) and here for the support history.

5 Comments

I still don't understand how a IEEE 754 float stored in a given endianness can become a NaN after swapping its bytes by a system with the opposite endianness. The FAQ mentions that this can happen even with systems that have the same endianness for integers and FP. Without an example, I don't believe their claims.
@cesss It can happen independently of system integer endianness. In fact, it is inherent of the IEEE 754 definitions. NaNs are defined as numbers with exponents filled with 1s and a non-zero mantissa, which you could get in practice after a bytes reversal. For example, on my amd64 system (little-endian), the four bytes 7f ffffffa0 0 0 are interpreted as 5.75752e-41 but the reverse gives me a signalling NaN.
Obviously, if you byte-swap a float which had the correct endianness in the host system and then you try to read it on the same host, what you get is garbage. But that's not the point. The point is that the FAQ claims that if you read such a byte-swapped float on a host that has the opposite endianness of the first host, you can get a NaN. AFAIK, that's not possible, because the second host is reading the float in exactly the same way as the first host with its native endianness (sure, the 2nd host reads it byte-swapped, but it also has the opposite endianness, so it sees the same)
@cesss I think you are attributing claims to the FAQ that it doesn't make. For instance, they never talk about transmission between two systems with different endianness. I think that the main concern here is that you can have invalid transitory values in the endianness conversion process with floats that you cannot have with integers, and users probably experienced side effects due to this.
Ah, I think that clears it, thanks! I never store a swapped float as a float type, so I didn't think of that. When they mention the code could generate NaNs, I thought they meant at the end of the transmission, not in the middle. Thanks!
-1

quick hack:

#define FBIT32_REVERSE(val) htonl(*(long*) &val) 

1 Comment

I don't see the need of a macro here. The issue with "hacks" is that they are dirty, and often unsafe to use (or at least too hard to make it right) while simply calling the (regular) function would work perfectly well and would be way cleaner and clearer. Moreover you should mention that htonl() is a Windows-specific function. When you add an answer to a 13 years old question which already have an accepted answer, be sure to make it really useful.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.