Best way of checking if a floating point is an integer

后端 未结 12 1841
陌清茗
陌清茗 2020-12-25 13:07

[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

12条回答
  •  隐瞒了意图╮
    2020-12-25 13:48

    This test is good:

    if (   f >= std::numeric_limits::min()
        && f <= std::numeric_limits::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::min() <= d
          && d <= std::numeric_limits::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 
      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::min() and std::numeric_limits::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.

提交回复
热议问题