Casting (-)float to unsigned long gives inconsistent results

When casting a -negative float value to unsigned long sometimes I get zero, but sometimes I get the negative float binary representation interpreted as a unsigned long. Either of these behaviors seems reasonable, but I can't figure why it gives different behaviors on some floats. In the code below I get the float 2508.0 three ways U, V, W:

  1. using a for loop to accumulate 15 sums of 167.2 (i.e., 15*167.2=2508). [U]
  2. set directly. [V=2508.0;]
  3. set the hex representation to be the same as 1. [W=(float)0x451CBFFF;]

If I cast the negative of the first one to unsigned long I get 0xFFFFF635. The other two give 0x0. The sum and setting directly give different hex representations, which is why I did the 3rd test. The fact that their hex representation is different makes sense because of round off, but why do two variables with the same hex representation get converted differently is hard to understand. Any explanations would be appreciated.

To get the hex representation I used:

Serial.print(*((unsigned long*)&U),HEX);

Here is the code to reproduce:

void setup() {
  Serial.begin(9600);
  float U=0,V=0,W=0;
  for (int n = 0; n < 15; n++) {
    U += 167.2;
  }
  V=2508.0;                        // 15*167.2
  unsigned long tmp=0x451CBFFF;  // same hex rep of U
  W=*((float*)&tmp);
  
  Serial.print(*((unsigned long*)&U),HEX);
  Serial.println(" [sum]");
  Serial.print(*((unsigned long*)&V),HEX);
  Serial.println(" [2508.0]");
  Serial.print(*((unsigned long*)&W),HEX);
  Serial.println(" [same hex rep]");
  Serial.println("==================(long)[-U,-V,-W]==================");
  Serial.println((long)(-U),HEX);
  Serial.println((long)(-V),HEX);
  Serial.println((long)(-W),HEX);
  Serial.println("=============(unsigned long)[-U,-V,-W]==============");
  Serial.println((unsigned long)(-U),HEX);
  Serial.println((unsigned long)(-V),HEX);
  Serial.println((unsigned long)(-W),HEX);
}

void loop() {

}

Here is the output:

451CBFFF [sum]
451CC000 [2508.0]
451CBFFF [same hex rep]
==================(long)[-U,-V,-W]==================
FFFFF635
FFFFF634
FFFFF635
=============(unsigned long)[-U,-V,-W]==============
FFFFF635
0
0

Do you know for a fact that doing this is guaranteed by the language specification to provide defined behavior? It seems like an odd thing to be doing.

And, judging by the warnings generated, the compiler is not all that happy with that code.

I'm having trouble imagining why you would want to do that.

A floating point number will have a sign bit, mantissa and exponent crammed into those 4 bytes. I suppose if the exponent is zero it may have a similar representation to unsigned long.

I don't know for sure, but I just don't see why it is not well defined. I use this construct to extract the binary representation of a variable.

1 Like

That's odd I don't get any warnings.

I actually wanted to get the binary representation, but sometimes it gives 0 instead.

Here ya go:

void setup() {
  const size_t numBytes = sizeof(float);
  uint8_t binary[numBytes];
  float v = 2508.0;

  Serial.begin(115200);
  delay(2000);

  memcpy(binary, &v, numBytes);
  Serial.print("0x");
  for (uint8_t b : binary) {
    if (b < 0x10) {
      Serial.print(0);
    }
    Serial.print(b, HEX);
  }
  Serial.println();
}

void loop() {
}

Output:

0x00C01C45

Check It Here. When you do, you'll need to reverse the byte order, enter: 451CC000

1 Like

Never trust computers; it's people like me who programme them!
Your mistake is in expecting 15 * 167.2 to be 2508.0.
It isn't. Not with binary floating point, anyway. Add the following code:

  Serial.print(F("U=")); Serial.println(U,9);
  Serial.print(F("V=")); Serial.println(V,9);
  Serial.print(F("W=")); Serial.println(W,9);

and see how untrustworthy computers are.
On my Nano I get

U=2507.999755859
V=2508.000000000
W=2507.999755859

I agree. I'm ok with 15 * 167.2 != 2508.0 because of the binary representation roundoff. My problem is that when U and W have the same binary representation, but casting gives a different answer.

Ah! Sorry. That's the optimising compiler's fault.
Change

  float U=0,V=0,W=0;

to

  volatile  float U=0,V=0,W=0;

and all will be well.
Like I said ... don't trust computers
<EDIT Maybe this issue should be reported to ... someone. Who? />

Ok. That makes sense. Thanks for the help.

Thanks. This is basically an expanded version of what I did. Here is a concise version:

void setup() {
  Serial.begin(9600);
  float V = 2508.0;
  Serial.print(*((unsigned long*)&V), HEX);
}

output: 451CC000

The 'volatile' keyword fixed my problem. It seems that the optimizer did something.

No. What you did was dereference a type-punned pointer. Using volatile may have "fixed" it in this case. But, you're still likely invoking undefined behavior and there's no guarantee it will work every time. memcpy() will.

2 Likes

I see your point. Thanks for the tip. In my real application, timing is pretty tight, so I'm not sure if I have time to memcpy().

Note that casting float to uint32_t results in 0; please explain where a pointer is used with the code

#define USE_VOLATILE_xx // #define USE_VOLATILE
#if defined(USE_VOLATILE)
  volatile float W=0;
#else
  float W=0;
#endif

  Serial.print(F("sizeof(int32_t)=")); Serial.println(sizeof(int32_t),DEC);
  Serial.print(F("sizeof(uint32_t)=")); Serial.println(sizeof(uint32_t),DEC);
  Serial.print(F("sizeof(float)=")); Serial.println(sizeof(float),DEC);

  Serial.println();
  Serial.println("============= W = 2507.9997f ==============");
  W = 2507.9997f;

  Serial.print(F("W=")); Serial.println(W,9);

  Serial.println("==================(int32_t)(-W)==================");

  Serial.println((int32_t)(-W), DEC);
  Serial.println((int32_t)(-W),HEX);

  Serial.println("=============(uint32_t)(-W)==============");
  Serial.println((uint32_t)(-W), DEC);
  Serial.println((uint32_t)(-W),HEX);

producing

sizeof(int32_t)=4
sizeof(uint32_t)=4
sizeof(float)=4

============= W = 2507.9997f ==============
W=2507.999755859
==================(int32_t)(-W)==================
-2507
FFFFF635
=============(uint32_t)(-W)==============
0
0

NB Defining USE_VOLATILE does cast the float to uint32_t, as shown in output

sizeof(int32_t)=4
sizeof(uint32_t)=4
sizeof(float)=4

============= W = 2507.9997f ==============
W=2507.999755859
==================(int32_t)(-W)==================
-2507
FFFFF635
=============(uint32_t)(-W)==============
4294964789
FFFFF635

BTW I now have a composite volatile float V and float W test code, that makes A:B comparison easier:

  volatile float V=0;
  float W=0;

  Serial.print(F("sizeof(int32_t)=")); Serial.println(sizeof(int32_t),DEC);
  Serial.print(F("sizeof(uint32_t)=")); Serial.println(sizeof(uint32_t),DEC);
  Serial.print(F("sizeof(float)=")); Serial.println(sizeof(float),DEC);

  Serial.println();
  Serial.println("============= V & W = 2507.9997f ==============");
  V = 2507.9997f;
  W = 2507.9997f;
  Serial.print(F("V=")); Serial.println(V,9);
  Serial.print(F("W=")); Serial.println(W,9);

  Serial.println("==================(int32_t)(-V, -W)==================");
  Serial.print(F("V=")); Serial.print((int32_t)(-V), DEC); Serial.print('\t'); Serial.println((int32_t)(-V),HEX);
  Serial.print(F("W=")); Serial.print((int32_t)(-W), DEC); Serial.print('\t'); Serial.println((int32_t)(-W),HEX);

  Serial.println("=============(uint32_t)(-V, -W)==============");
  Serial.print(F("V=")); Serial.print((uint32_t)(-V), DEC); Serial.print('\t'); Serial.println((uint32_t)(-V),HEX);
  Serial.print(F("W=")); Serial.print((uint32_t)(-W), DEC); Serial.print('\t'); Serial.println((uint32_t)(-W),HEX);

with output on Arduino Nano clone <EDIT ARDUINO=10815, AVR H/W=<avr/iom328p.h> />:

sizeof(int32_t)=4
sizeof(uint32_t)=4
sizeof(float)=4

============= V & W = 2507.9997f ==============
V=2507.999755859
W=2507.999755859
==================(int32_t)(-V, -W)==================
V=-2507	FFFFF635
W=-2507	FFFFF635
=============(uint32_t)(-V, -W)==============
V=4294964789	FFFFF635
W=0	0

<EDIT#2 same results with static_cast<uint32_t>/>

I don't know. But, since what you're doing is so odd (IMO), it's not worth spending any more of my time on.

1 Like

Can you explain why you need to do this? Certainly not for printing, if memcpy taking too long would be a concern.