检查浮点是否为整数的最佳方法

P45*_*ent 22 c++ floating-point c++11 floating-point-conversion

[有一些问题,但没有一个答案特别明确,有些问题与当前的C++标准已经过时了].

我的研究表明,这些是用于检查浮点值是否可以转换为整数类型的主要方法T.

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

  2. 使用std::fmod以提取剩余时间和测试平等为0.

  3. 使用std::remainder和测试相等为0.

第一个测试假定定义f了一个T实例的强制转换.不是真实std::int64_tfloat,例如.

使用C++ 11,哪一个最好?有没有更好的办法?

Mar*_*ork 14

结论:

答案是std::trunc(f) == f在比较所有这些方法时使用时差是微不足道的.即使我们在下面的示例中编写的特定IEEE展开代码在技术上是两倍的速度,我们只能说快1纳秒.

从长远来看,维护成本会显着提高.因此,使用维护者更容易阅读和理解的解决方案更好.

以毫秒为单位在一组随机数字上完成12,000,000次操作的时间:

  • IEEE细分:18
  • std::trunc(f) == f                                  32
  • std::floor(val) - val == 0                35
  • ((uint64_t)f) - f) == 0.0                  38
  • std::fmod(val, 1.0) == 0                     87

制定出结论.

浮点数是两部分:

mantissa:      The data part of the value.
exponent:      a power to multiply it by.

such that:

   value =  mantissa * (2^exponent)
Run Code Online (Sandbox Code Playgroud)

因此,指数基本上是我们要将"二进制点"向下移动尾数的二进制数字.正值将其向右移动,负值将其向左移动.如果二进制点右边的所有数字都是零,那么我们有一个整数.

如果我们假设IEEE 754

我们应该注意,这个表示值被归一化,以便尾数中的最高有效位被移位为1.由于该位总是被设置,所以它实际上没有存储(处理器知道它在那里并相应地进行补偿).

所以:

如果exponent < 0那时你肯定没有整数,因为它只能表示一个小数值.如果exponent >= <Number of bits In Mantissa>那时肯定没有分形部分而且它是一个整数(尽管你可能无法将它保存在蚂蚁中).

否则我们必须做一些工作.如果int那么你可以通过表示一个整数,如果exponent >= 0 && exponent < <Number of bits In Mantissa>在下半部分(下面定义)全部为零.

附加作为归一化的一部分127被添加到指数中(使得在8位指数字段中不存储负值).

#include <limits>
#include <iostream>
#include <cmath>

/*
 *  Bit  31      Sign
 *  Bits 30-23   Exponent
 *  Bits 22-00   Mantissa
 */
bool is_IEEE754_32BitFloat_AnInt(float val)
{
    // Put the value in an int so we can do bitwise operations.
    int  valAsInt = *reinterpret_cast<int*>(&val);

    // Remember to subtract 127 from the exponent (to get real value)
    int  exponent = ((valAsInt >> 23) & 0xFF) - 127;

    int bitsInFraction = 23 - exponent;
    int mask = exponent < 0
                    ? 0x7FFFFFFF
                    : exponent > 23
                         ? 0x00
                         : (1 << bitsInFraction) - 1;

    return !(valAsInt & mask);
}
/*
 *  Bit  63      Sign
 *  Bits 62-52   Exponent
 *  Bits 51-00   Mantissa
 */
bool is_IEEE754_64BitFloat_AnInt(double val)
{
    // Put the value in an long long so we can do bitwise operations.
    uint64_t  valAsInt = *reinterpret_cast<uint64_t*>(&val);

    // Remember to subtract 1023 from the exponent (to get real value)
    int  exponent = ((valAsInt >> 52) & 0x7FF) - 1023;

    int bitsInFraction = 52 - exponent;
    uint64_t mask = exponent < 0
                    ? 0x7FFFFFFFFFFFFFFFLL
                    : exponent > 52
                        ? 0x00
                        : (1LL << bitsInFraction) - 1;

    return !(valAsInt & mask);
}

bool is_Trunc_32BitFloat_AnInt(float val)
{
    return (std::trunc(val) - val == 0.0F);
}

bool is_Trunc_64BitFloat_AnInt(double val)
{
    return (std::trunc(val) - val == 0.0);
}

bool is_IntCast_64BitFloat_AnInt(double val)
{
    return (uint64_t(val) - val == 0.0);
}

template<typename T, bool isIEEE = std::numeric_limits<T>::is_iec559>
bool isInt(T f);

template<>
bool isInt<float, true>(float f) {return is_IEEE754_32BitFloat_AnInt(f);}

template<>
bool isInt<double, true>(double f) {return is_IEEE754_64BitFloat_AnInt(f);}

template<>
bool isInt<float, false>(float f) {return is_Trunc_64BitFloat_AnInt(f);}

template<>
bool isInt<double, false>(double f) {return is_Trunc_64BitFloat_AnInt(f);}

int main()
{
    double  x = 16;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 16.4;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 123.0;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 0.0;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 2.0;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 4.0;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 5.0;
    std::cout << x << "=> " << isInt(x) << "\n";

    x = 1.0;
    std::cout << x << "=> " << isInt(x) << "\n";
}
Run Code Online (Sandbox Code Playgroud)

结果:

> ./a.out
16=> 1
16.4=> 0
123=> 1
0=> 1
2=> 1
4=> 1
5=> 1
1=> 1
Run Code Online (Sandbox Code Playgroud)

运行一些时序测试.

测试数据生成如下:

(for a in {1..3000000};do echo $RANDOM.$RANDOM;done ) > test.data
(for a in {1..3000000};do echo $RANDOM;done ) >> test.data
(for a in {1..3000000};do echo $RANDOM$RANDOM0000;done ) >> test.data
(for a in {1..3000000};do echo 0.$RANDOM;done ) >> test.data
Run Code Online (Sandbox Code Playgroud)

修改main()来运行测试:

int main()
{
    // ORIGINAL CODE still here.
    // Added this trivial speed test.

    std::ifstream   testData("test.data");  // Generated a million random numbers
    std::vector<double>  test{std::istream_iterator<double>(testData), std::istream_iterator<double>()};
    std::cout << "Data Size: " << test.size() << "\n";
    int count1 = 0;
    int count2 = 0;
    int count3 = 0;

    auto start = std::chrono::system_clock::now();
    for(auto const& v: test)
    {   count1 += is_IEEE754_64BitFloat_AnInt(v);
    }
    auto p1 = std::chrono::system_clock::now();
    for(auto const& v: test)
    {   count2 += is_Trunc_64BitFloat_AnInt(v);
    }
    auto p2 = std::chrono::system_clock::now();
    for(auto const& v: test)
    {   count3 += is_IntCast_64BitFloat_AnInt(v);
    }

    auto end = std::chrono::system_clock::now();

    std::cout << "IEEE  " << count1 << " Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(p1 - start).count() << "\n";
    std::cout << "Trunc " << count2 << " Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(p2 - p1).count()    << "\n";
    std::cout << "Int Cast " << count3 << " Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - p2).count()   << "\n";    }
Run Code Online (Sandbox Code Playgroud)

测试显示:

> ./a.out
16=> 1
16.4=> 0
123=> 1
0=> 1
2=> 1
4=> 1
5=> 1
1=> 1
Data Size: 12000000
IEEE  6000199 Time: 18
Trunc 6000199 Time: 32
Int Cast 6000199 Time: 38
Run Code Online (Sandbox Code Playgroud)

IEEE代码(在这个简单的测试中)似乎超过了截断方法并生成相同的结果.但是时间量是微不足道的.超过1200万次通话我们看到了14毫秒的差异.

  • 或多或少是编写`std :: trunc(f)== f`的更困难的方法.此帖也没有真正回答问题,因为它没有评估建议的选项. (4认同)

Bat*_*eba 10

使用std::fmod(f, 1.0) == 0.0其中f可以是一个float,doublelong double.如果您在使用floats 时担心不需要的浮点促销的虚假效果,那么使用其中一种1.0f或更全面的

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

这显然会在编译时强制调用正确的重载.返回值std::fmod(f, ...)将在[0,1]范围内,并且比较0.0完成整数检查是完全安全的.

如果结果f 整数,那么尝试强制转换之前,请确保它在您选择的类型的允许范围内:否则您可能会调用未定义的行为.我知道你已经熟悉std::numeric_limits哪些可以帮到你了.

我对使用的保留std::remainder可能是(i)我是Luddite,以及(ii)在部分实现C++ 11标准的某些编译器中没有,例如MSVC12.我不喜欢涉及演员表的解决方案,因为符号隐藏了相当昂贵的操作,你需要事先检查安全性.如果你必须采用你的第一选择,至少更换C型演员用static_cast<T>(f);

  • @SlodgeMonster:呃,你怎么知道谁是专家,谁不是?到目前为止,您有两个正确的答案. (4认同)