Best way of checking if a floating point is an int

2019-02-01 21:49发布

[There are a few questions on this but none of the answers are particularly definitive and several are out of date with the current C++ standard].

My research shows these are the principal methods used to check if a floating point value can be converted to an integral type T.

  1. if (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max() && f == (T)f))

  2. using std::fmod to extract the remainder and test equality to 0.

  3. using std::remainder and test equality to 0.

The first test assumes that a cast from f to a T instance is defined. Not true for std::int64_t to float, for example.

With C++11, which one is best? Is there a better way?

12条回答
时光不老,我们不散
2楼-- · 2019-02-01 21:58

If your question is "Can I convert this double to int without loss of information?" then I would do something simple like :

template <typename T, typename U>
bool CanConvert(U u)
{
  return U(T(u)) == u;
}

CanConvert<int>(1.0) -- true
CanConvert<int>(1.5) -- false
CanConvert<int>(1e9) -- true
CanConvert<int>(1e10)-- false
查看更多
放荡不羁爱自由
3楼-- · 2019-02-01 21:59

Some other options to consider (different compilers / libraries may produce different intrinsic sequences for these tests and be faster/slower):

bool is_int(float f) { return floor(f) == f; }

This is in addition to the tests for overflow you have...

If you are looking to really optimize, you could try the following (works for positive floats, not thoroughly tested): This assumes IEEE 32-bit floats, which are not mandated by the C++ standard AFAIK.

bool is_int(float f)
{
    const float nf = f + float(1 << 23);
    const float bf = nf - float(1 << 23);
    return f == bf;
}
查看更多
霸刀☆藐视天下
4楼-- · 2019-02-01 22:01

This test is good:

if (   f >= std::numeric_limits<T>::min()
    && f <= std::numeric_limits<T>::max()
    && f == (T)f))

These tests are incomplete:

using std::fmod to extract the remainder and test equality to 0.

using std::remainder and test equality to 0.

They both fail to check that the conversion to T is defined. Float-to-integral conversions that overflow the integral type result in undefined behaviour, which is even worse than roundoff.

I would recommend avoiding std::fmod for another reason. This code:

int isinteger(double d) {
  return std::numeric_limits<int>::min() <= d
      && d <= std::numeric_limits<int>::max()
      && std::fmod(d, 1.0) == 0;
}

compiles (gcc version 4.9.1 20140903 (prerelease) (GCC) on x86_64 Arch Linux using -g -O3 -std=gnu++0x) to this:

0000000000400800 <_Z9isintegerd>:
  400800:       66 0f 2e 05 10 01 00    ucomisd 0x110(%rip),%xmm0        # 400918 <_IO_stdin_used+0x18>
  400807:       00
  400808:       72 56                   jb     400860 <_Z9isintegerd+0x60>
  40080a:       f2 0f 10 0d 0e 01 00    movsd  0x10e(%rip),%xmm1        # 400920 <_IO_stdin_used+0x20>
  400811:       00
  400812:       66 0f 2e c8             ucomisd %xmm0,%xmm1
  400816:       72 48                   jb     400860 <_Z9isintegerd+0x60>
  400818:       48 83 ec 18             sub    $0x18,%rsp
  40081c:       d9 e8                   fld1
  40081e:       f2 0f 11 04 24          movsd  %xmm0,(%rsp)
  400823:       dd 04 24                fldl   (%rsp)
  400826:       d9 f8                   fprem
  400828:       df e0                   fnstsw %ax
  40082a:       f6 c4 04                test   $0x4,%ah
  40082d:       75 f7                   jne    400826 <_Z9isintegerd+0x26>
  40082f:       dd d9                   fstp   %st(1)
  400831:       dd 5c 24 08             fstpl  0x8(%rsp)
  400835:       f2 0f 10 4c 24 08       movsd  0x8(%rsp),%xmm1
  40083b:       66 0f 2e c9             ucomisd %xmm1,%xmm1
  40083f:       7a 22                   jp     400863 <_Z9isintegerd+0x63>
  400841:       66 0f ef c0             pxor   %xmm0,%xmm0
  400845:       31 c0                   xor    %eax,%eax
  400847:       ba 00 00 00 00          mov    $0x0,%edx
  40084c:       66 0f 2e c8             ucomisd %xmm0,%xmm1
  400850:       0f 9b c0                setnp  %al
  400853:       0f 45 c2                cmovne %edx,%eax
  400856:       48 83 c4 18             add    $0x18,%rsp
  40085a:       c3                      retq
  40085b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  400860:       31 c0                   xor    %eax,%eax
  400862:       c3                      retq
  400863:       f2 0f 10 0d bd 00 00    movsd  0xbd(%rip),%xmm1        # 400928 <_IO_stdin_used+0x28>
  40086a:       00
  40086b:       e8 20 fd ff ff          callq  400590 <fmod@plt>
  400870:       66 0f 28 c8             movapd %xmm0,%xmm1
  400874:       eb cb                   jmp    400841 <_Z9isintegerd+0x41>
  400876:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40087d:       00 00 00

The first five instructions implement the range check against std::numeric_limits<int>::min() and std::numeric_limits<int>::max(). The rest is the fmod test, accounting for all the misbehaviour of a single invocation of the fprem instruction (400828..40082d) and some case where a NaN somehow arose.

You get similar code by using remainder.

查看更多
我只想做你的唯一
5楼-- · 2019-02-01 22:06

The problem with:

  if (   f >= std::numeric_limits<T>::min()
      && f <= std::numeric_limits<T>::max()
      && f == (T)f))

is that if T is (for example) 64 bits, then the max will be rounded when converting to your usual 64 bit double :-( Assuming 2's complement, the same is not true of the min, of course.

So, depending on the number of bits in the mantisaa, and the number of bits in T, you need to mask off the LS bits of std::numeric_limits::max()... I'm sorry, I don't do C++, so how best to do that I leave to others. [In C it would be something along the lines of LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG) -- assuming T is long long int and f is double and that these are both the usual 64 bit values.]

If the T is constant, then the construction of the two floating point values for min and max will (I assume) be done at compile time, so the two comparisons are pretty straightforward. You don't really need to be able to float T... but you do need to know that its min and max will fit in an ordinary integer (long long int, say).

The remaining work is converting the float to integer, and then floating that back up again for the final comparison. So, assuming f is in range (which guarantees (T)f does not overflow):

  i  = (T)f ;         // or i  = (long long int)f ;
  ok = (i == f) ;

The alternative seems to be:

  i  = (T)f ;         // or i  = (long long int)f ;
  ok = (floor(f) == f) ;

as noted elsewhere. Which replaces the floating of i by floor(f)... which I'm not convinced is an improvement.

If f is NaN things may go wrong, so you might want to test for that too.

You could try unpacking f with frexp() and extract the mantissa as (say) a long long int (with ldexp() and a cast), but when I started to sketch that out it looked ugly :-(


Having slept on it, a simpler way of dealing with the max issue is to do: min <= f < ((unsigned)max+1) -- or min <= f < (unsigned)min -- or (double)min <= f < -(double)min -- or any other method of constructing -2^(n-1) and +2^(n-1) as floating point values, where n is the number of bits in T.

(Serves me right for getting interested in a problem at 1:00am !)

查看更多
兄弟一词,经得起流年.
6楼-- · 2019-02-01 22:07

what about converting types like this?

bool can_convert(float a, int i)
{
  int b = a;
  float c = i;
  return a == c;
}
查看更多
我想做一个坏孩纸
7楼-- · 2019-02-01 22:11

Use std::fmod(f, 1.0) == 0.0 where f is either a float, double, or long double. If you're worried about spurious effects of unwanted floating point promotions when using floats, then use either 1.0f or the more comprehensive

std::fmod(f, static_cast<decltype(f)>(1.0)) == 0.0

which will force, obviously at compile time, the correct overload to be called. The return value of std::fmod(f, ...) will be in the range [0, 1) and it's perfectly safe to compare to 0.0 to complete your integer check.

If it turns out that f is an integer, then make sure it's within the permitted range of your chosen type before attempting a cast: else you risk invoking undefined behaviour. I see that you're already familiar with std::numeric_limits which can help you here.

My reservations against using std::remainder are possibly (i) my being a Luddite and (ii) it not being available in some compilers partially implementing the C++11 standard, such as MSVC12. I don't like solutions involving casts since the notation hides that reasonably expensive operation and you need to check in advance for safety. If you must adopt your first choice, at least replace the C-style cast with static_cast<T>(f);

查看更多
登录 后发表回答