P45*_*ent 22 c++ floating-point c++11 floating-point-conversion
[有一些问题,但没有一个答案特别明确,有些问题与当前的C++标准已经过时了].
我的研究表明,这些是用于检查浮点值是否可以转换为整数类型的主要方法T.
if (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max() && f == (T)f))
使用std::fmod以提取剩余时间和测试平等为0.
使用std::remainder和测试相等为0.
第一个测试假定定义f了一个T实例的强制转换.不是真实std::int64_t到float,例如.
使用C++ 11,哪一个最好?有没有更好的办法?
Mar*_*ork 14
答案是std::trunc(f) == f在比较所有这些方法时使用时差是微不足道的.即使我们在下面的示例中编写的特定IEEE展开代码在技术上是两倍的速度,我们只能说快1纳秒.
从长远来看,维护成本会显着提高.因此,使用维护者更容易阅读和理解的解决方案更好.
以毫秒为单位在一组随机数字上完成12,000,000次操作的时间:
std::trunc(f) == f 32std::floor(val) - val == 0 35((uint64_t)f) - f) == 0.0 38std::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毫秒的差异.
Bat*_*eba 10
使用std::fmod(f, 1.0) == 0.0其中f可以是一个float,double或long 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);