-4

I have no idea how to convert float variable into an integer without warnings. I have no std::round because of the old compiler on my project, disabling the warning with #pragma is a bad idea for such a usual case. I receive the following warning:

Warning 4 warning C4244: 'initializing' : conversion from 'float' to 'size_t', possible loss of data

Tvyxen
  • 21
  • 1
  • 2
  • 5
    C or C++? they are different languages. – Sourav Ghosh Dec 31 '18 at 09:30
  • 2
    It also depends on what you mean by "convert". Do you want only the integer part of the floating-point number? Do you want the `unsigned` representation of the sign-bit, normalized exponent and mantissa that make up the floating-point number? Do your want the `ceil` value or the `floor` value? Or, do you just want to validate the float is within the range of `size_t` and then do `size_t val = (size_t)fpnum;`? – David C. Rankin Dec 31 '18 at 09:47
  • It's hard to imagine why you would need to do that. You might be trying to solve the wrong problem, why exactly do you need to do this? – john Dec 31 '18 at 09:48

6 Answers6

2

Since size_t must be an unsigned type, you first need to check explicitly if your float is negative: The behaviour on converting a float that's less than or equal to -1 to an unsigned type is undefined.

The second job you need to do is to check that the float is within the upper range of size_t. Again, the behaviour on attempting to convert a float to an unsigned type that's out of range is undefined. So many folk assume that wrap-around behaviour applies. It doesn't if the original type is floating point.

Only then should you attempt a conversion.

The best thing to do is to use round() and cast the result using a C-style cast. Ignore any warnings at that point. Many C++ compilers warn less if an explicit static_cast is used. You could use that instead if that causes the warning to get disappeared.

If round() is not available then consider using a 3rd party mathematics library. Rolling your own version is tempting but these functions are not trivial to build and you could unwittingly give up portability (which of course, a standard library function is allowed to do). See Why does Math.round(0.49999999999999994) return 1? which goes into some detail.

These rules apply to both C and C++.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
2

to convert float to size_t without warnings

Simple solution:

size_t sz = (size_t) some_float;

The explicit cast will quiet the warning.

Yet this misses 2 potential failures: imprecision and overflow.

  • Imprecision

Should f contain a factional amount, code could round, drop the fraction, declare error, or what ever code wants. (Apparently OP wants to round.)

  • Overflow

Test if in the range (-1.0 ... SIZE_MAX + 1). E.g. [-0.999.... 4,294,967,295.999...]. The cast is well defined here. (size_t) some_float with an out-of-range float is undefined behavior. Take advantage that SIZE_MAX + 1 is expected to be exactly represented as a float as in the following.

#define SIZE_MAX_P1_FLOAT  ((SIZE_MAX/2 + 1)*2.0f)

size_t float_to_size_t(float f) {
  if (f <= -1.0f) {
    return 0.0f;  // f is too small
  }
  if (f >= SIZE_MAX_P1_FLOAT) {
    return SIZE_MAX;  // f is too great
  }
  size_t sz = (size_t) f;

  // Test for imprecision
  if ((float)sz != f) {
    // Handle as desired.
    // Perhaps round nearest with ties to even.
    float frac = f - (float)sz;
    if (frac >= 0.5f) {
      if (sz == SIZE_MAX) {
        return SIZE_MAX;  // f is too great
      }
      if (frac > 0.5f || sz%2) {
        sz++;
      } 
    } else if (frac <= -0.5f) {
      if (frac < 0.5f) {
        return 0; // f is too small
      } 
    }
  }

  return sz;
}

Some improvements

#include <errno.h>
#include <stdint.h>
// Return best (rounded to nearest - ties to even) float to size_t conversion possible.
// Set ERANGE when `f` was too small, too great.  Return 0, SIZE_MAX.
// Set EDOM when `f` is NAN. Return 0.
size_t float_rounded_to_size_t(float f);

/*
 * Converting `float f` to `size_t` with a cast is well defined when
 * mathematically f > -1.0 and f < SIZE_MAX + 1.
 *  
 * SIZE_MAX_P1_FLOAT: SIZE_MAX + 1 can overflow integer math, yet since 
 * SIZE_MAX is a Mersenne number
 * (http://mathworld.wolfram.com/MersenneNumber.html),
 * (SIZE_MAX/2 + 1) is computable and is exactly half of SIZE_MAX + 1.
 * 
 * To return a rounded to nearest size_t, 
 * SIZE_MAX + 0.5 <= f also leads to out-of-range. Instead of 
 * `f < SIZE_MAX_P1_FLOAT - 0.5f` for upper limit test, use 
 * `f - SIZE_MAX_P1_FLOAT < -0.5f`
 * to prevent precision loss near the upper bound.
 * 
 * On rare platforms, FLT_MAX < SIZE_MAX+1 and an upper bound check
 * on finite `f` is not needed.
 * Below code does not yet account for that.  
 */

// `float` value 1 past SIZE_MAX:
#define SIZE_MAX_P1_FLOAT  ((SIZE_MAX/2 + 1)*2.0f)

size_t float_rounded_to_size_t(float f) {
  // In range?
  if (f >= -0.5f && f - SIZE_MAX_P1_FLOAT < -0.5f) {
    size_t sz = (size_t) f;
    float frac = f - (float) sz;
    if (frac > 0.5f || (frac >= 0.5f && sz % 2)) {
      sz++;
    }
    return sz;
  }
  if (f >= 0.0f) {
    errno = ERANGE;
    return SIZE_MAX;  // f is too great
  }
  if (f < 0.0f) {
    errno = ERANGE;
    return 0;  // f is too negative
  }
  errno = EDOM;
  return 0;  // f is not-a-number
}
Bathsheba
  • 231,907
  • 34
  • 361
  • 483
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • `if (f <= 1.0f) {` nobbles 0.5 for example. Trivial fix, but let's have you do it. – Bathsheba Dec 31 '18 at 14:27
  • surely `f < 0.5f`? Then you eliminate the UB when converting a negative float to an unsigned. – Bathsheba Dec 31 '18 at 14:29
  • @Bathsheba No UB when converting `-0.999.... to zero` to an unsigned. But I do need to look over rounding `-0.999.... to -0.5` – chux - Reinstate Monica Dec 31 '18 at 14:33
  • 1
    15-15. Yes you're correct, no UB in that region. I've amended my answer. And have an upvote! – Bathsheba Dec 31 '18 at 14:34
  • The new code is nice indeed. We're not *quite* there on portability due to the possibility of the unsigned type being larger than the maximum float value, but the answer already mentions that. If anything this illustrates how non-trivial writing a portable `round` function is. – Bathsheba Dec 31 '18 at 19:40
  • @Bathsheba Alternatives when `FLT_MAX < SIZE_MAX+1`. 1) Use a half `SIZE_MAX_P1_FLOAT` as that helps with 128 bit `size_t` and common `float`. 2) Use `double` with `#define SIZE_MAX_P1_FP ((SIZE_MAX/2 + 1)*2.0)` and push the problem to `DBL_MAX`. That does not theoretically help, but practically does. 3) Employ a fair amount of macro magic 4) Perform a one time run-time calculation to assess the need for the upper limit -likely the most robust. – chux - Reinstate Monica Dec 31 '18 at 20:26
0

You can remove the warning with an explicit cast: size_t s = (size_t)0.5;.

In some cases, getting rid of some of the sanity warnings can be tricky. For example clang generates a warning for this perfectly fine program:

#include <math.h>
#include <stdio.h>

int main() {
    size_t s = (size_t)floor(0.5);
    return s != 0;
}

Compilation with clang -Weverything produces:

float-conv.c:5:24: warning: cast from function call of type 'double' to non-matching type 'size_t'
      (aka 'unsigned long') [-Wbad-function-cast]
    size_t s = (size_t)floor(0.5);
                       ^~~~~~~~
1 warning generated.

You can remove this warning by casting an expression instead of the function call result directly:

#include <math.h>
#include <stdio.h>

int main() {
    size_t s = (size_t)+floor(0.5);
    return s != 0;
}

Note however that the unary + might convert -0.0 to +0.0 but this has no impact on the converted value as a size_t.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
-1

well if you really need it you can use

size_t s=(size_t)your_float;

but you need to be careful for dataloss as the warning said :)

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
Spinkoo
  • 2,080
  • 1
  • 7
  • 23
-2

You can use a cast to convert the value. Casting is a language feature that allows you to inform the compiler that you're aware of the consequences of your actions and that it shouldn't warn you about them.

In C casts are of the form ( type ), where type is the destination type, so in your example you would do this:

float  f = 10.0f;
size_t s = ( size_t )f;

In C++ casting is more specific to the type of casting you want to do. static_cast, dynamic_cast, const_cast, and reinterpret_cast exist. When converting a numerical value, you would use static_cast as follows:

float  f = 10.0f;
size_t s = static_cast< size_t >( f );

C++ casting is a lot safer than C-style casting (which still exists in C++ for backwards compatibility), so if you're writing C++ code, you should always use the C++ cast operators.

In addition to the above, when casting from a floating point value to an integer value you should consider any rounding that may occur. (Which is what the compiler is warning you about: You can - and probably will - lose numerical accuracy by converting a float to a size_t.) For these types of conversion (implicit or cast), the rounding is always toward zero, so if you want to round to the closest integer value, you will have to subtract 0.5f from your float if it is negative, and add 0.5f if it is positive. So in C++ your final code would look something like this:

if( f < 0.0f )
{
    s = static_cast< size_t >( f - 0.5f );
}
else
{
    s = static_cast< size_t >( f + 0.5f );
}

Following the conversation below, you can also call std::lround from the C++11 standard library to perform the conversion and take care of any rounding issues you might have otherwise encountered. It should be noted, however, that lround returns a long which might still cause a truncation warning when assigning to a size_t.

James
  • 161
  • 7
  • 1
    Don't ever think about adding or subtracting 0.5. See https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1 There was a bug in Java up to version 5 that was down to this. – Bathsheba Dec 31 '18 at 09:53
  • This isn't Java. – James Dec 31 '18 at 09:54
  • 1
    Yes thank you for that peal of wisdom. I'm talking about the JIT, specifically the one written in C. – Bathsheba Dec 31 '18 at 09:55
  • My point still stands. The default rounding mode in Java is round to nearest, which is why this was a problem for Java. In C++ it's round to zero, so while you might encounter some edge cases that feel a bit iffy, you won't get an incorrect answer. Also, the code in question in Java was still Java code, not due to a bug in the JVM written in C. – James Dec 31 '18 at 10:01
  • Sorry but it doesn't. Did you not study the accepted answer in the link I gave you? I'm struggling to understand why you think the rounding technique you give it acceptable given the weight of evidence to the contrary. – Bathsheba Dec 31 '18 at 10:02
  • @Bathsheba, how do you then round in C? I couldn't find a `round()` function. – Paul Ogilvie Dec 31 '18 at 10:22
  • @Bathsheba, btw, very interesting link about Java rounding. – Paul Ogilvie Dec 31 '18 at 10:23
  • Ok, I (partially) stand corrected. I finally managed to reproduce the issue in C++. The issue in question occurs with `double`, but not `float` - at least not in its original form. But that said, it's an extreme edge case that 99.9999% of the time you won't encounter or notice. Let's be honest, the loss of precision from converting to `size_t` is far greater. – James Dec 31 '18 at 10:24
  • @PaulOgilvie: Ain't there one in ``? I'm amazed that the sun guys fell for that rounding bug for so long. Every scientific progammer knows that adding floating point then applying a horrendously discontinuous function to it (which is what rounding is) is bound to end in tears! – Bathsheba Dec 31 '18 at 10:25
  • @Bathsheba, I checked math.h but it only has floor and ceil. So rounding in C _must_ be done by adding 0.5 and then either floor or cast to int. – Paul Ogilvie Dec 31 '18 at 13:17
  • C++11 has a `round` function. – James Dec 31 '18 at 13:18
  • I've added this to the answer above. – James Dec 31 '18 at 13:22
  • 1
    A program that is correct only 99.9999% of the time is a program with a bug. An answer with a bug gets a vote down. – Eric Postpischil Dec 31 '18 at 13:39
  • @PaulOgilvie: Nope, you borrow one from a third party mathematics library. – Bathsheba Dec 31 '18 at 13:49
  • I guess it really depends on where you draw the line with regards to your definition of a bug. It's a rounding error. If you're concerned with 100% accuracy in your calculations you wouldn't be using floats or doubles in the first place. – James Dec 31 '18 at 14:19
  • @Bathsheba, how about writing your own one, like converting the Java solution to C? – Paul Ogilvie Dec 31 '18 at 14:19
  • @PaulOgilvie: That Java one assumes IEEE754 (which is portable Java). Unfortunately neither C nor C++ do. – Bathsheba Dec 31 '18 at 14:21
  • @Bathsheba, any suggestion (for a library version or source code)? – Paul Ogilvie Dec 31 '18 at 14:22
  • @PaulOgilvie: Doing this portably and fast is a pain. Personally I'd upgrade the compiler. Alternatively assert that you are targetting IEEE754 and nick the Java code. – Bathsheba Dec 31 '18 at 14:28
  • 1
    @James: The definition of a bug is that a program does not conform to its specification. Failing to return a specified value, such as an input rounded to the nearest integer, is a bug. The notion that somebody using floating-point is not concerned with accuracy is incorrect. Many participants in Stack Overflow treat floating-point arithmetic as somewhat random, but that is incorrect. Floating-point arithmetic is specified, not random, and knowledgeable practitioners can use those specifications to craft good code. – Eric Postpischil Dec 31 '18 at 15:11
-2

You can use this macro which i adopted by

Code

#include <float.h>

#define roundd(x) ((x) < FLT_MIN-0.5 || (x) > FLT_MAX+0.5 ?\
printf("range error\n"): (x)>=0?(size_t)((x)+0.5):(size_t)((x)-0.5))

int main()
{  
    float  f = 10.9f;
    size_t s = roundd(f);

    printf("%zu\n",s);
    return 0;
}

Use %zu in printf() to print size_t.

EsmaeelE
  • 2,331
  • 6
  • 22
  • 31