2

I have some floats (IEEE-754) that I want to initialize. The floats are fetched by another device (automagically) which runs big endian where I am using little endian and I can't change that.

Normally I would just swap with some built in function, but they are all run-time functions. I'd perfer not having to have an init() function just to swap endianess and it would be great if I could use it for const initializations also.

Something that result in this would be perfect:

#define COMPILE_TIME_SWAPPED_FLOAT(x) (...) const float a = COMPILE_TIME_SWAPPED_FLOAT(1.0f);

Any great ideas?

Tajen
  • 43
  • 4
  • 1
    You say "fetched by another device" but then you have `1.0f` hardcoded in your code. Please clarify what is going on – M.M Sep 22 '17 at 12:47
  • My guess would be that even if you didn't use a macro most reasonable optimizing compilers would still be able to do this optimization at compile time. Of course you should check the compiler output to confirm. – Michael Mior Sep 22 '17 at 12:50
  • In this particular case, it is a constant timeout for a watchdog for this particular device, which the remote system wants to know (for unknown reasons). The remote system request the data over a RS485 bus and expects the data to be in a certain endianess. – Tajen Sep 22 '17 at 12:56
  • Instead of "fetched by another device" you should say "passed to another device" – M.M Sep 22 '17 at 13:12
  • If you're still interested in a compile time approach for a limited range of values (approx. 1e-1 to 1e3), I could come up with one. – Armali Sep 26 '17 at 12:20

3 Answers3

2

Compile time/macro swap of endian-ness of float in c99

OP has other problem with using "reverse" float as a float

A local variable of type float encoding the "reverse" endian floating point value cannot certainly be initialized as a float. Many of the values in reverse byte order would correspond to a Not-A-Number (NAN) in the local float. The assignment may not be stable (bit pattern preserving). It could be:

// not a good plan
float f1 = some_particulate_not_a_number_bit_pattern;
float f2 = some_other_particulate_not_a_number_bit_pattern;

Instead the local "reversed" endian float should just be a uint32_t, 4-byte structure/union or 4-byte array initialized in some way with a float.

// Demo of why a reversed `float` can fail
// The Not-a-numbers bit to control signaling NaN vs. quiet NaN isn't certainly preserved. 

int main(void) {
  for (;;) {
    union {
      int32_t i32;
      int32_t u32;
      float f;
    } x,y;
    x.i32 = rand();
    y.f = x.f;
    if (x.u32 ^ y.u32) {
      // If bit pattern preserved, this should never print
      //                                 v-------+---- biased exponent max (NaN)
      //                                 |-------|v--- signaling/quiet bit
      // On my machine output is always x1111111 1?xxxxxx xxxxxxxx xxxxxxxx
      printf("%08x\n",   (unsigned) x.u32);
      printf("%08x\n\n", (unsigned) y.u32);
    }
  }
}

Output

7f8181b1
7fc181b1
...

The below uses a compound literal to meet OP's goal. First initialize a union's float member with the desired float. Then extract it byte-by-byte from its uint8_t member (per desired endian) to initialize a new compound literal's uint8_t array member. Then extract the uint32_t. Works for local variables.

#include <stdint.h>
#include <stdio.h>

typedef uint32_t float_reversed;

typedef union  {
  uint8_t u8[4];
  float_reversed u32;
  float f;
} endian_f;

#define ENDIAN_FN(_f,_n)  ( (endian_f){.f=(_f)}.u8[(_n)] )
#define ENDIAN_F(_f) ((endian_f){ \
    ENDIAN_FN(_f,3), ENDIAN_FN(_f,2), \
    ENDIAN_FN(_f,1), ENDIAN_FN(_f,0)}.u32)

void print_hexf(void *f) {
  for (size_t i=0; i<sizeof f; i++) {
    printf("%02X", ((unsigned char *)f)[i]);
  }
  puts("");
}

int main(void) {
  float f1 = 1.0f;
  print_hexf(&f1);

  float_reversed f1r = ENDIAN_F(f1);
  print_hexf(&f1r);
  float_reversed f2r = ENDIAN_F(1.0);
  print_hexf(&f2r);
}

Output

0000803F
3F800000
3F800000
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Unfortunately the requirement is to have this working in global scope, which it won't I suspect. – alk Sep 22 '17 at 14:26
  • @Armali Post ammended – chux - Reinstate Monica Sep 22 '17 at 14:29
  • Ok, its does not seem to be requirement, but still it "*won't work*" on global scope, right? ;-) – alk Sep 22 '17 at 14:31
  • @alk I see no global scope variable solution yet. Hence this answer's "Works for local variables." – chux - Reinstate Monica Sep 22 '17 at 14:36
  • 1
    Excellent answer! I was looking for a union initializer, but compound literal expressions are the right way to destructure the bitwise representation. I am not sure if this approach has 100% defined behavior because type-punning via `union`s still confuses me. How come no-one else voted this up? – chqrlie Sep 22 '17 at 16:40
  • @chqrlie _Compound literals_ from C99 are still new-ish and have sluggish wide acceptance. Using a `union` through an `unsigned char[]` is quite well defined as there are no trap, padding nor aliasing issues. – chux - Reinstate Monica Sep 22 '17 at 16:54
  • 1
    It is a pity acceptance is so slow, probably because they are not part of even recent C++ releases. Also beats be why your solution cannot be used for global scope. It is about time one could use more expressivity at global scope without ugly macros. – chqrlie Sep 22 '17 at 16:58
  • @chqrlie Hmmmm: at global scope, C allows `endian_f x1 = { .f = 3.14f };` - no surprise. gcc warns about `endian_f x2 = (endian_f ) { .f = 3.14f };` - a small surprise yet fails `float f1 = (endian_f ) { .f = 3.14f } .f;`. – chux - Reinstate Monica Sep 22 '17 at 18:14
  • 1
    It is not done compile time, but I think it is the closest we are going to get. – Tajen Sep 25 '17 at 06:04
  • @Tajen With code like `float_reversed f1r = ENDIAN_F(f1);` C does not specify that this code must be computed at compile time nor does it specify that it must be done at run time. An optimizing compile will certainly make for efficient code. YMMV. – chux - Reinstate Monica Sep 25 '17 at 13:48
0

I'd say having the preprocessor to swap bytes of some non byte variable isn't possible.

The preprocessor does not know about data types and their representation on byte-level.

alk
  • 69,737
  • 10
  • 105
  • 255
  • Would you say that any swapping of bytes is impossible by the preprocessor? – Tajen Sep 22 '17 at 12:38
  • @Tajen: If you'd pass it bytes it might be able to swap them, but it cannot parse any other data type into bytes, and then swap those. Answer adjusted. – alk Sep 22 '17 at 12:39
  • I could convert the float value to a uint32_t hex value and also swap it myself. but then I need the preprocessor to copy it to the float without converting it. That should be possible - in theory at least. – Tajen Sep 22 '17 at 12:42
  • @Tajen: This might work at run-time, but won't during compilation, which is a prerequisite to have this work on global scope. – alk Sep 22 '17 at 13:30
0

If the endian reversal code is available to be inlined then any half decent optimizing compiler will work out the reversed value at compile time.

Taking the reversal code from https://stackoverflow.com/a/2782742/2348315 :

inline 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;
}

And using it in away that compiler can see all the details:

float reversed10(){
  const float reversed = ReverseFloat(10.0f);
  return reversed;
}

Compiles to:

reversed10():
        vmovss  xmm0, DWORD PTR .LC0[rip]
        ret
.LC0:
        .long   8257

with GCC 7.1 with -O2 enabled.

You can try other compilers over here: https://godbolt.org/g/rFmJGP

PeterSW
  • 4,921
  • 1
  • 24
  • 35
  • Thanks @Armali I've corrected to the correct C syntax. – PeterSW Sep 22 '17 at 14:08
  • A corner problem with `float reversed = ReverseFloat(10.0f)` is that the return from `ReverseFloat()` may be a [signaling Not-a-number](https://stackoverflow.com/q/18118408/2410359) and cause problems with the assignment or perhaps even in `return retVal;` – chux - Reinstate Monica Sep 22 '17 at 16:38