1

I am writing a simple function to check the 1's compliment of a floating number. This is the code I have written to verify the value:

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

int main()
{
 float input = 25.456;
 printf("input val = %f\n",input);
 uint32_t temp = (uint32_t)input;
 uint32_t toggleval = ~temp;
 
 uint32_t checker = ~toggleval;
 float output = (float)checker;
 printf("output val = %f\n",output);

 return 0;
}

After running this code, I can see the output as

input val = 25.455999
output val = 25.000000

Why are those decimal places different? I am expecting the same values as the input float value? Anything wrong here?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
user2986042
  • 1,098
  • 2
  • 16
  • 37
  • 1
    `uint32_t temp = (uint32_t)input;` is making `temp` an integer with a value of `25`. Why would you expect it to preserve the fraction part of the data? You can remove the inversion part and will get the same result. So the "1's complement" part of the question is a red herring. – Eugene Sh. Sep 06 '22 at 15:14
  • I presume he wanted a bitwise copy, for which `memcpy` should be used and not a cast. – Ben Voigt Sep 06 '22 at 15:17
  • I want to get the same float data after inversion. How can i achieve that ? – user2986042 Sep 06 '22 at 15:19
  • 2
    _1's compliment_ sounds confusingly like the integer encoding [ones' compliment](https://en.wikipedia.org/wiki/Ones%27_complement). I think _bit-wise complement_ would be clearer here. – chux - Reinstate Monica Sep 06 '22 at 15:21
  • Tip: to well compare the exact value of a `float`s, use `"%.7g"` or even better `"%a"`. `"%f"` prints about half of all `float` as some zero - not that useful for exact compares. – chux - Reinstate Monica Sep 06 '22 at 15:34
  • presumably related to https://stackoverflow.com/q/73613781/1358308 – Sam Mason Sep 06 '22 at 15:52

3 Answers3

2

Your attempts at "type-punning" don't do what you think. The uint32_t temp = (uint32_t)input assignment does not return the binary representation of the float as a unit32_t; rather, it converts the value of the float to an unsigned integer (by truncation of the non-integral part). The other cast (back to a float) does a similar (but reversed) conversion.

For such type-punning (in C but not in C++) you can use a union that has a float and a uint32_t occupying the same memory; then, you can write to one and read from the other:

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

int main()
{
    union { float f; uint32_t u; } pun;
    float input = 25.456f;
    printf("input val = %f\n", input);

    pun.f = input; // Write the float part ...
    uint32_t temp = pun.u; // ... but read as a unit32_t
    uint32_t toggleval = ~temp;

    uint32_t checker = ~toggleval;
    pun.u = checker; // Write the unit32_t ...
    float output = pun.f; // ... but read as a float
    printf("output val = %f\n", output);

    return 0;
}

Related reading: Unions and type-punning

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • I believe that *strictly speaking* even this and `memcpy` methods are UB, as the memory accessed as `float` in the end was last modified as `uint32_t` object, that is having an effective type of `uint32_t` – Eugene Sh. Sep 06 '22 at 15:27
  • @EugeneSh. Nope. Such union-based type punning is perfectly well-defined in C (see the linked post). (Although it *may* be possible to weird things happening on mixed-endian architectures.) – Adrian Mole Sep 06 '22 at 15:28
  • 1
    @EugeneSh. That sounds like C++. IMO, this is correct C - as long as `float/uint32_t` are the same size. – chux - Reinstate Monica Sep 06 '22 at 15:28
  • Yes, you are right. The [footnote 95](http://port70.net/~nsz/c/c11/n1570.html#note95) is making it clear enough. – Eugene Sh. Sep 06 '22 at 15:41
0

When you do this:

uint32_t temp = (uint32_t)input; 

You're converting a float to a uint32_t. When you convert a floating point type to an integer type, any fractional part is truncated. So temp contains the value 25. You then (after flipping the bits twice) assign that value to output which gets printed.

dbush
  • 205,898
  • 23
  • 218
  • 273
-2

Reinterpret cast instead of convert

assert(sizeof(float) == sizeof(uint32_t));
uint32_t temp = *(uint32_t *)&input;
...
float output = *(float *)&checker;
SargeATM
  • 2,483
  • 14
  • 24
  • 1
    This way is undefined behavior because of the violation of the *strict aliasing* rule. Use `memcpy` instead. – Eugene Sh. Sep 06 '22 at 15:24
  • From a practical standpoint and this is more of a rant against the standard, it is hard for me to imagine a scenario where just **reading** the bytes into another variable would make issues for a compiler. I can understand changing the bytes and the compiler not knowing it would need to reload a register again. Also I have seen lots of code like this in the wild and compilers would be unwise to implement un-optional optimizations that silently broke code like this. – SargeATM Sep 06 '22 at 15:49
  • 1
    At the very least, I'm leaving the answer up because many C programmers I know would do it this way and it would be good for them to see that it can at the very least limit optimizations or produce future breaking code. – SargeATM Sep 06 '22 at 15:52
  • 1
    "it is hard for me to imagine a scenario ..." --> I feel your pain. Modern compilers take advantage of UB to optimize code in ways that surprised me. This answer's code could be optimized to `float output;`, `output` simply not assigned. Best not to suggest UB tricks of the past for current coding practice. – chux - Reinstate Monica Sep 06 '22 at 16:25