Converting floating point to unsigned int while preserving order

三世轮回 提交于 2021-02-08 03:01:50

问题


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.


回答1:


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.




回答2:


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.




回答3:


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


来源:https://stackoverflow.com/questions/52370587/converting-floating-point-to-unsigned-int-while-preserving-order

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!