26

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?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Blade3
  • 4,140
  • 13
  • 41
  • 57

11 Answers11

41

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;
}
Gregor Brandt
  • 7,659
  • 38
  • 59
  • 1
    This will break on gcc with optimizations due to pointer aliasing. Do not do that, use union for such casting. I've been there, I've hit it. – Tomek May 07 '10 at 11:41
  • 10
    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. – Gregor Brandt May 07 '10 at 13:01
  • @gbrand the code is legal but it doesn't mean it works. have a look here: http://xania.org/200712/cpp-strict-aliasing. You can also see how to union "cast" there. – Tomek May 07 '10 at 16:13
  • 26
    @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). – James McNellis May 31 '10 at 18:20
  • @Tomek: Wouldn't using a union break some other rule as well? IIRC only one union member can be valid at any given time. – dalle May 31 '10 at 18:56
  • I realize that accessing the union through the member which was not the latest to be written to is bad (AFAIR it is dreaded UB) but it seems to work. This is why I call a template to cast this way evil_cast ;). @James: accessing as an array of char and through a pointer to char is NOT the same. For the aliasing I think going through char * is safe (I recall seeing it somewhere) but for me it is as bad as going through union. And honestly - if you want to fiddle with bits at such low level you are going to hit UB or IDB sooner or later. Which means you are at compiler's mercy. – Tomek May 31 '10 at 19:49
  • 12
    @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. – James McNellis Jun 01 '10 at 01:32
  • 13
    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. – kriss Dec 05 '10 at 09:00
  • 5
    @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. – Ed S. Sep 14 '12 at 20:11
  • 1
    The union idiom is so widespread that any compiler that breaks it will fail to compile most large software. – Joseph Garvin Dec 04 '18 at 22:17
  • 1
    reversing the bytes of a floating point number does not necessarily produce another valid floating point number. – user1050755 Mar 10 '22 at 17:54
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;
}
Benjamin
  • 121
  • 1
  • 4
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;
}
Sniggerfardimungus
  • 11,583
  • 10
  • 52
  • 97
  • 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. – Sniggerfardimungus May 06 '10 at 20:15
  • 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; =] – Sniggerfardimungus May 08 '10 at 01:01
  • 2
    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. – Suma May 14 '10 at 13:19
  • That said, I would probably prefer a more readable version with four shifts ored together. There are some platforms however where shift performance is worse for higher shift counts (PPC), therefore the one they have is likely to perform better. – Suma May 14 '10 at 13:23
  • 2
    `*arg = n;`? Is assigning to a dereferenced void pointer going to work without casting? I would expect the compiler to complain. – blubberdiblub Aug 25 '19 at 05:44
  • If your compiler complains (@blubberdiblub) about it, you can silence the warning/error. I don't care about the _type_ of what it points to - in fact, I'm explicitly working around that - only _what_ it actually points to. – Sniggerfardimungus Sep 06 '19 at 00:29
8

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;
}
  • 1
    Shouldn't you put the assignment dst.b <-> src.b into a loop? – jjmerelo Oct 29 '19 at 10:06
  • 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 – Antonio Cañas Vargas Oct 29 '19 at 13:16
  • 5
    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. – UKMonkey Feb 13 '20 at 14:26
  • 1
    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. – Antonio Cañas Vargas Feb 26 '20 at 10:35
  • reversing the bytes of a floating point number does not necessarily produce another valid floating point number. – user1050755 Mar 10 '22 at 17:54
  • See https://stackoverflow.com/questions/11373203/accessing-inactive-union-member-and-undefined-behavior for a discussion regarding the undefined bahviour in C++. – MarcusS Aug 17 '23 at 11:08
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.

jscharf
  • 5,829
  • 3
  • 24
  • 16
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;
}
infval
  • 318
  • 3
  • 4
  • 1
    reversing the bytes of a floating point number does not necessarily produce another valid floating point number. – user1050755 Mar 10 '22 at 17:54
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.

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
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.

Suma
  • 33,181
  • 16
  • 123
  • 191
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]
NPULSENET
  • 191
  • 4
  • reversing the bytes of a floating point number does not necessarily produce another valid floating point number. – user1050755 Mar 10 '22 at 17:54
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.

vvanpelt
  • 791
  • 1
  • 6
  • 13
  • 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 Aug 29 '21 at 12:27
  • @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 `1`s 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. – vvanpelt Aug 29 '21 at 13:50
  • 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 Aug 29 '21 at 14:33
  • 1
    @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 `float`s that you cannot have with integers, and users probably experienced side effects due to this. – vvanpelt Aug 29 '21 at 15:01
  • 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! – cesss Aug 29 '21 at 15:43
-1

quick hack:

#define FBIT32_REVERSE(val) htonl(*(long*) &val)
trebhtux
  • 1
  • 1
  • 2
    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. – Fareanor Aug 18 '23 at 12:28