2

I have found a lot of answers on SO focusing on converting float to int.

I am manipulating only positive floating point values. One simple method I have been using is this:

unsigned int float2ui(float arg0) {
    float f = arg0;
    unsigned int r = *(unsigned int*)&f;
    return r;
}

The above code works well yet it fails to preserve the numeric order. By order I mean this:

  float f1 ...;
  float f2 ...;
  assert( ( (f1 >= f2) && (float2ui(f1) >= float2ui(f2)) ) ||
          ( (f1 <  f2) && (float2ui(f1) < vfloat2ui(f2)) ));

I have tried to use unions with the same results. Any idea? I use Homebrew gcc 5.3.0.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
N. Wells
  • 143
  • 1
  • 12
  • 1
    What are you trying to do? If you just want to have your `int` with a truncated value of `float`, this is not the way. The way would be just `unsigned int r = f;` Otherwise it's just an undefined behavior. – Eugene Sh. Sep 17 '18 at 15:00
  • 1
    Floating point and integer have very different representations. Treating one as the other results in undefined behavior; it doesn't matter if you do it with pointers or unions. The only thing you can do portably is `r = (int)f;`, although this fails if `f` is larger than `UINT_MAX`. – Barmar Sep 17 '18 at 15:03
  • Are you interested in getting the numeric value of the variable, or its bitwise representation? You can't do both. – Tim Randall Sep 17 '18 at 15:12
  • Aside: You might prefer to assert `(f1 >= f2) == (float2ui(f1) >= float2ui(f2))` – Tim Randall Sep 17 '18 at 15:15
  • No NaNs? Comparison operations involving NaNs return false https://stackoverflow.com/questions/31225264/what-is-the-result-of-comparing-a-number-with-nan – Paul Floyd Sep 17 '18 at 16:14
  • @Barmar re: [this fails if `f` is larger than `UINT_MAX`](https://stackoverflow.com/questions/52370587/converting-floating-point-to-unsigned-int-while-preserving-order#comment91685916_52370587) is amiss. I'd say if the "if _truncated_ `f` is larger than `INT_MAX`". (different constant, and truncated `f`) – chux - Reinstate Monica Sep 17 '18 at 16:54
  • @chux Not sure I understand the distinction you're making. I'm talking about the abstract numeric values, not representational values. – Barmar Sep 17 '18 at 17:52
  • 1
    The code invokes undefined behaviour. – too honest for this site Sep 17 '18 at 18:54
  • Have you checked that `float`s and `int`s use the same endianess on your architecture? If not, you have to swap bytes. If both types have the most significant bit in the same place, then copying the bits from a `float` to an `int` will preserve order. (Naturally assuming twos compliment `int`) – HAL9000 Sep 17 '18 at 22:42

3 Answers3

4

The code you're using, as writen, has undefind behavior. If you want to access the representation of floats semi-portably (implementation-defined, well-defined assuming IEEE 754 and that float and integer endianness match), you should do:

uint32_t float2ui(float f){
    uint32_t r;
    memcpy(&r, &f, sizeof r);
    return r;
}

For non-negative values, this mapping between floating point values and representation is order-preserving. If you think you're seeing it fail to preserve order, we'll need to see exactly what values you think are a counterexample.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • How does this differ from the use of a union (float, unsigned int)? – N. Wells Sep 17 '18 at 15:57
  • 2
    @PaulFloyd: It might in C++, but C permits reinterpreting bytes through a union. – Eric Postpischil Sep 17 '18 at 16:26
  • Detail: "For non-negative values _(not NaNs)_ ... is order-preserving". OTOH, one may consider NaNs as lacking value. – chux - Reinstate Monica Sep 17 '18 at 16:41
  • C vaguely support sunion reinterpretation via somewhat underspecified and inconsistent wording, so I prefer the `memcpy` approach which is just as efficient on any decent compiler and clearly well-defined. – R.. GitHub STOP HELPING ICE Sep 17 '18 at 16:52
  • Your use of `memcpy` is no way better than type punning via a `union`. In fact type punning via `memcpy` is not even allowed. – too honest for this site Sep 17 '18 at 18:53
  • 1
    @toohonestforthissite: Sure it's allowed. There is some subtlety for the resulting effective type of objects with allocated storage, but for objects with declared type, none of that comes into play. The type does not change, and the resulting value is simply the result of interpreting the bytes you stored to the representation in the actual type. In general this could be a trap representation, but that would be implementation-defined, not undefined/disallowed, and for `uint32_t` it specifically cannot be because there are no spare bits to be padding. – R.. GitHub STOP HELPING ICE Sep 17 '18 at 19:30
  • @R..GitHubSTOPHELPINGICE How to handle negative float values? – jeffreyveon Mar 04 '20 at 15:59
  • @jeffreyveon: That's probably a topic for a new question, but basically you can either use a signed int32 and invert the low 31 bits if the sign bit is set. A similar approach works if you want unsigned but you have to add the 0x80000000 bias. – R.. GitHub STOP HELPING ICE Mar 04 '20 at 16:09
  • @R..GitHubSTOPHELPINGICE I've posted a new question here with the details: https://stackoverflow.com/questions/60530255/convert-float-to-int64-t-while-preserving-ordering – jeffreyveon Mar 04 '20 at 16:12
3

If f1 and f2 are floating points, and f1 <= f2, and (int)f1 and (int)f2 are valid conversions, then (int)f1 <= (int)f2.

In other words, a truncation to an integral type never swaps an order round.

You could replace float2ui with simply (int)arg0, having checked the float is in the bounds of an int.

Note that the behaviour of float to int and float to unsigned is undefined if the truncated float value is out of the range for the type.

Your current code - somehow intrepreting the float memory as int memory - has undefined behaviour. Even type-punning through a union will give you implementation defined results; note in particular that sizeof(int) isn't necessarily the same as sizeof(float).

If you are using an IEEE754 single-precision float, a 32 bit 2's complement int with no trap representation, a positive value for conversion, consistent endianness, and some allowances for the various patterns represented by NaN and +-Inf, then the transformation effected by a type pun is order preserving.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • @R..Forgive me but "converting float to int" normally means converting a float to an int?! – Bathsheba Sep 17 '18 at 15:09
  • Given the tags (including `ieee-754`) it's clear that OP wants to access the representation, and assuming that, yes it's implementation-defined but OP has specified the implementation constraint. – R.. GitHub STOP HELPING ICE Sep 17 '18 at 15:09
  • I wouldn't say OP is 'clearly' trying to do anything. My interpretation is the same as @Bathsheba – Tim Randall Sep 17 '18 at 15:10
  • BTW there are very good reasons for order-preserving transformations like this, for example integrating the values as part of a sort key. – R.. GitHub STOP HELPING ICE Sep 17 '18 at 15:12
  • @chqrlie: That's a very good point; probably the most important one in my amended list. – Bathsheba Sep 17 '18 at 15:37
  • How do I KNOW I'm "using an IEEE754 single-precision float"? And BTW, @R is right, I am using it as a sort key. – N. Wells Sep 17 '18 at 15:54
  • @N.Wells: Check the value of `__STDC_IEC_559__`, from C11 onwards. – Bathsheba Sep 17 '18 at 15:55
  • Thanks, but is there a "codable" way of doing that in C? – N. Wells Sep 17 '18 at 15:58
  • Minor: "2's complement" does not appear to to be needed in the final requirement list. given "a positive value for conversion". – chux - Reinstate Monica Sep 17 '18 at 16:49
  • @N.Wells: All interesting C implementations use IEEE 754 for the `float` and `double` types. A few use weird things for `long double`. Only very obscure embedded-microcontroller/dsp-oriented implementations or very very oldschool retrocomputing will use anything else for `float`. But if you really want to check, `#ifdef __STDC_IEC_559__`. – R.. GitHub STOP HELPING ICE Sep 17 '18 at 16:55
  • @Bathsheba: `__STDC_IEC_559__` indicates the implementation implements floating-point types according to IEC 60559 (effectively IEEE 754) **and** implements arithmetic according to IEC 60559. Many implementations implement the types according to IEC 60559, but few, if any, implement the arithmetic according to IEC 60559, and therefore few defined `__STDC_IEC_559__`. The symbol is not a reliable indicator of whether or not IEEE-754 types are used. – Eric Postpischil Sep 17 '18 at 17:29
  • 1
    @chux: I wonder if the OP is disallowing a signed integer zero when they say "positive"? Quite often "positive" is used when "non-negative" would be much better. For non-negative cases you are indeed correct. – Bathsheba Sep 18 '18 at 07:27
  • @EricPostpischil; My reading though is that if the symbol is defined, then `float` is certainly IEEE754 format, although the converse is not necessarily the case. – Bathsheba Sep 18 '18 at 07:27
  • @Bathsheba: Yes, and to whom is that useful? What compiler sets the symbol? If one tests `__STDC_IEC_559__` to ask whether IEEE-754 types are in use, the possible answers are “yes” and “maybe.” And the “yes” answer is rare or nonexistent. So the test usually produces “maybe.” Most C implementations use IEEE-754 types, but this test will not reveal that. It is not a useful test. – Eric Postpischil Sep 18 '18 at 13:32
0

Extracting the bits from a float using a union should work. There is some discussion if the c standard actually supports this. But whatever the standard says, gcc seems to support it. And I would expect there is too much existing code that demands it, for the compilers to remove support.

There are some things you must be aware of when putting a float in an int and keeping order.

  1. Funny values like nan does not have any order to keep
  2. floats are stored as magnitude and sign bit, while ints are twos compliment (assuming a sane architecture). So for negative values, you must flip all the bits except the sign bit
  3. If float and int does not have the same endianess on your architecture, you must also convert the endianess

Here is my implementation, tested with gcc (Gentoo 6.4.0-r1 p1.3) 6.4.0 on x64

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

union ff_t
{
  float f;
  unsigned char a[4];
  int i;
};

int same_endianess = 0;

void
swap_endianess(union ff_t *ff)
{
  if (!same_endianess)
    {
       unsigned char tmp;
       tmp = ff->a[0];
       ff->a[0] = ff->a[3];
       ff->a[3] = tmp;

       tmp = ff->a[1];
       ff->a[1] = ff->a[2];
       ff->a[2] = tmp;
    }
}

void
test_endianess()
{
  union ff_t ff = { ff.f = 1 };

  if (ff.i == 0x3f800000)
    same_endianess = 1;
  else if (ff.i == 0x803f)
    same_endianess = 0;
  else
    {
      fprintf(stderr, "Architecture has some weird endianess");
      exit(1);
    }
}

float
random_float()
{
   float f = random();
   f -= RAND_MAX/2;

   return f;
}

int
f2i(float f)
{
  union ff_t ff = { .f = f };

  swap_endianess(&ff);

  if (ff.i >= 0)
    return ff.i;

  return ff.i ^ 0x3fffffff;
}

float
i2f(int i)
{
  union ff_t ff;
  if (i >= 0)
    ff.i = i;
  else
    ff.i = i ^ 0x3fffffff;

  swap_endianess(&ff);

  return ff.f;
}


int
main()
{
  /* Test if floats and ints uses the same endianess */
  test_endianess();

  for (int n = 0; n < 10000; n++)
    {
       float f1 = random_float();
       int i1 = f2i(f1);
       float f2 = random_float();
       int i2 = f2i(f2);

       printf("\n");
       printf("0x%08x,  %f\n", i1, f1);
       printf("0x%08x,  %f\n", i2, f2);

       assert ( f1 == i2f(i1));
       assert ( f2 == i2f(i2));

       assert ( (f1 <= f2) == (i1 <= i2));
    }
}
HAL9000
  • 2,138
  • 1
  • 9
  • 20