5

How do I check whether a float can be represented as an integral type without invoking undefined behavior by just casting? This is forbidden by §4.9.1:

A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type,

There's this question for C, but the accepted answer clearly causes undefined behavior (first by just plain casting and the later by using the union hack, which makes the whole thing very questionable to me).

I can see how it'd be hard to have a fully compliant solution, but one that is implementation defined (to assume IEEE-754 floats) would be acceptable too.

Community
  • 1
  • 1
Voo
  • 29,040
  • 11
  • 82
  • 156
  • Would it not be sufficient to check that the original value is less than `numeric_limits::max()` (with obvious extension to negative values)? Once it is within range, then truncated value should be representable. – Igor Tandetnik Sep 27 '15 at 17:54
  • @Igor But how would the comparison work? That would again involve some implicit conversion as far as I can see which would either cause false results or UB., – Voo Sep 27 '15 at 18:05
  • The comparison would convert from integer to float. That shouldn't be a problem on a typical implementation - even 2^64 is within range of a 32-bit IEEE `float`. For extra safety, convert both values to `double` before comparison. You'd need a 1000-bit integral type to get close to the limits of 64-bit IEEE `double`. – Igor Tandetnik Sep 27 '15 at 18:15
  • Oops, I'm off by an order of magnitude. It takes some 100 bits to overflow a `double`, so a 128-bit integral type (available on some implementations) may pose a problem. – Igor Tandetnik Sep 27 '15 at 18:38
  • @Igor Mhm true. If the int doesn't fit into the float we'd get infinity (2^64 may fit into a 32-bit float, but 2^63-1 wouldn't for example), which would then guarantee the comparison to fail. Seems safe even for 128-bit numbers. – Voo Sep 27 '15 at 18:50
  • @IgorTandetnik: IEEE double has an 11-bit exponent with a range -1022..+1023, so it takes a 1024 bit integer to overflow a double. However, it only has 53 bits of mantissa, so any 54+ bit integer will be rounded when converted to a `double`, losing precision. IEEE single has an 8-bit exponent, so it takes 128 bits to overflow. – Chris Dodd Sep 27 '15 at 19:46
  • 2
    I previously answered but deleted the answer, because the referenced question appears to have a nice and conforming answer http://stackoverflow.com/a/17822304/34509 – Johannes Schaub - litb Sep 27 '15 at 21:42
  • 1
    @JohannesSchaub-litb: Good find, but it would be nice to have a proper C++ version (generic, not assuming specific types). So I would argue not to close it as a duplicate. +1 for whoever does the translation. – MSalters Sep 28 '15 at 10:47
  • @Johannes Hah missed that one, but I agree with MSalters that your answer would be quite valuable as a canonical C++ answer since it has quite a few advantages compared to the C answer. – Voo Sep 28 '15 at 16:47

3 Answers3

0

Check truncf(x) == x. (The function is in <math.h>) This will compare true if and only if x has no fractional part. Then, compare x to the range of the type.

Example Code (Untested)

#include <cfenv>
#include <cmath>
#pragma STDC FENV_ACCESS on

template<class F, class N> // F is a float type, N integral.
  bool is_representable(const F x)
{
  const int orig_rounding = std::fegetround();

  std::fesetround(FE_TOWARDZERO);
  const bool to_return = std::trunc(x) == x &&
                         x >= std::numeric_limits<N>.min() &&
                         x <= std::numeric_limits<N>.max();
  std::fesetround(orig_rounding);
  return to_return;
}

With rounding set toward zero, the implicit conversion of the minimum and maximum values of the integral type to the floating-point type should not overflow. On many architectures, including i386, casting to long double will also provide enough precision to exactly represent a 64-bit int.

Davislor
  • 14,674
  • 2
  • 34
  • 49
0

You could use the %a format type specifier in snprintf() to get access to the mantissa and the exponent and you can then work out if the number is an integer or not and if it can fit into an integer type of a specific size. I used this method in a different problem.

Community
  • 1
  • 1
Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
0

Let's consider positive integers from 1 to MAX_FLOAT. For IEEE 754 The exponent represents the power of 2 (it's stored with a bias). IEE-754 format uses 1 sign bit, 8 biased exponent bits and 23 mantissa bits.The mantissa is a factional part of the number. The highest bit is 1/2, the next 1/4, the next 1/8 ...

Numbers from 1 to 1.999... have the same exponent. There is 1 integer in the range (1)

Numbers from 2 to 3.999... have the same exponent. There are 2 integers in the range (2,3). 3 has the highest mantissa bit set so it requires the leading mantissa bit. If any other lower mantissa bits are then its not an integer. Because it's 2 or 3 plus the the value of the fractional bit.

Numbers from 4 to 7.999... have the same exponent. There are 4 integers in the range (4,5,6,7) These use the 2 highest mantissa bits. If any other mantissa bits are set then its not an integer.

Numbers from 8 to 15.999... have the same exponent. There are 8 integers in the range (8,9,10,11,12,13,14,15) These use the 3 highest mantissa bits. If any other mantissa bits are set then its not an integer.

I hope you can see the pattern as you increase the exponent, the number of possible integers doubles. So ignore the n highest mantissa bits and test if the lower bits are set. If they are then the number is not an integer.

This tables shows the constant exponent value 0x40 plus the next highest bit, and that only the high order mantissa bits are set for integers

Float    Hex
4        0x40800000
5        0x40a00000
6        0x40c00000
7        0x40e00000

To convert a float to a UInt32

float x  = 7.0;
UInt32 * px = (UInt32*)&x;
UInt32    i = *px;
Tim Child
  • 2,994
  • 1
  • 26
  • 25
  • While not a bad idea the given implementation violates strict aliasing - that is fixable though - sadly Johannes deleted his answer that shows the same approach. – Voo Sep 29 '15 at 10:04