What is the best way to round large unsigned floating point numbers to integers in C++?

Viewed 406

I need to round a 64-bit double value to the nearest uint64_t in my code with the standard "math.h" midpoint rounding behaviour. Some of these numbers have the property:

  • Larger than LLONG_MAX
  • Less than or equal to ULLONG_MAX

I noticed there is no 'unsigned' version of the standard library rounding functions. Also, I suspect some of the tricks people use to do this no longer work when an intermediate double value is too large to fit into the mantissa.

What is the best way to do this conversion?

2 Answers

I would use std::round and then clamp to uint64 range. Also you need to decide what to do with NaN.

std::round does not convert to integer. Note that the built-in floating to integer conversion in C & C++ is truncating and undefined for out of range values.

std::round implements the standard "school" type rounding to the nearest integer with the midpoint cases being rounded outwards (e.g. round(1.5) == 2; round(-1.5) = -2).

The current rounding mode is ignored, also this is not the IEEE754 rounding to nearest. The latter would use round half to even and round the tie breaking cases up and down to reduce statistical bias.

For long long one would do something like this.

double iv = std::round(val);
if (std::isnan(iv)) {
    return 0LL;
} else if (iv > LLONG_MAX)
   return LLONG_MAX;    
} else if (iv < LLONG_MIN) {
   return LLONG_MIN;
} else {
   return (long long)iv;
}

The unsigned long long case is done correspondingly.

double iv = std::round(val);
if (std::isnan(iv)) {
    return 0ULL;
} else if (iv > ULLONG_MAX)   // ok, if you know your values are less this can be spared of course
   return ULLONG_MAX;    
} else if (iv < 0) {
   return 0ULL;
} else {
   return (unsigned long long)iv;
}

The simpliest way to round is to cast to the destination type:
Imagine you want to round 2.3 to the integer root, then you do:

return (int) 2.3;

So, in general, you do:

return (destination_type) x;

However, here you have the problem that you always round down, as you can see with this 2.7 example:

return (int) 2.7;
=> yields 2

If you want to round to the nearest, you need to add 0.5 and then round:

return (int) (2.7 + 0.5);
=> yields 3

So, when you want to round to whatever destination type, using closest rounding, you need:

return (destination_type) (x + 0.5);
Related