gpt4 book ai didi

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

转载 作者:IT老高 更新时间:2023-10-28 21:46:54 27 4
gpt4 key购买 nike

[对此有几个问题,但没有一个特别明确的答案,而且有几个与当前的 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_t 不正确至float ,例如。

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

最佳答案

结论:

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

从长远来看,维护成本会高得多。因此,最好使用维护者更容易阅读和理解的解决方案。

在一组随机数上完成 12,000,000 次操作所需的时间(以微秒为单位):

  • IEEE segmentation :                                             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

得出结论。

一个 float 由两部分组成:

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

such that:

value = mantissa * (2^exponent)

所以指数基本上是我们要将“二进制点”向下移动尾数的多少二进制数字。正值向右移动,负值向左移动。如果二进制点右边的所有数字都为零,那么我们就有一个整数。

如果我们假设 IEEE 754

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

所以:

如果 exponent < 0那么你肯定没有整数,因为它只能代表一个小数值。如果 exponent >= <Number of bits In Mantissa>那么肯定没有分形部分,它是一个整数(尽管您可能无法将它保存在 int 中)。

否则我们必须做一些工作。如果 exponent >= 0 && exponent < <Number of bits In Mantissa>如果 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";
}

结果:

> ./a.out
16=> 1
16.4=> 0
123=> 1
0=> 1
2=> 1
4=> 1
5=> 1
1=> 1

运行一些计时测试。

测试数据是这样生成的:

(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

修改 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"; }

测试表明:

> ./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

IEEE 代码(在这个简单的测试中)似乎击败了 truncate 方法并生成了相同的结果。 但是时间是微不足道的。超过 1200 万次调用我们在 14 毫秒内看到了差异。

关于c++ - 检查浮点是否为整数的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26341494/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com