[关于此问题有一些问题,但没有一个答案特别明确,有些问题与当前的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,哪一个最好?还有更好的方法吗?
答案 0 :(得分:14)
答案是使用std::trunc(f) == f
在比较所有这些方法时,时差是微不足道的。即使我们在下面的示例中编写的特定IEEE展开代码在技术上是两倍的速度,我们只会更快地谈论 1纳秒。
从长远来看,维护成本会显着提高。因此,使用维护者更容易阅读和理解的解决方案会更好。
在一组随机数字上完成12,000,000次操作的时间(以微秒为单位):
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)
因此,指数基本上是我们要将“二进制点”向下移动尾数的二进制数字。正值将其向右移动,负值将其向左移动。如果二进制点右边的所有数字都是零,那么我们有一个整数。
如果我们假设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代码(在这个简单的测试中)似乎击败了截断方法并生成相同的结果。 但时间量无关紧要。超过1200万次通话我们看到了14毫秒的差异。
答案 1 :(得分:10)
使用std::fmod(f, 1.0) == 0.0
,其中f
是float
,double
或long double
。如果您在使用float
时担心不受欢迎的浮点促销会产生虚假影响,请使用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)无法使用它。我不喜欢涉及演员表的解决方案,因为符号隐藏了相当昂贵的操作,你需要事先检查安全性。如果你必须采用你的第一选择,至少用static_cast<T>(f)
;
答案 2 :(得分:1)
这个测试很好:
if ( f >= std::numeric_limits<T>::min()
&& f <= std::numeric_limits<T>::max()
&& f == (T)f))
这些测试不完整:
using std::fmod to extract the remainder and test equality to 0.
using std::remainder and test equality to 0.
他们都未能检查是否已定义到T
的转换。溢出整数类型的浮点到积分转换会导致未定义的行为,这甚至比舍入更糟。
出于其他原因,我建议避免使用std::fmod
。这段代码:
int isinteger(double d) {
return std::numeric_limits<int>::min() <= d
&& d <= std::numeric_limits<int>::max()
&& std::fmod(d, 1.0) == 0;
}
编译(x86版本4.9.1 20140903(预发布)(GCC)在x86_64 Arch Linux上使用-g -O3 -std = gnu ++ 0x)到此:
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
前五条说明针对std::numeric_limits<int>::min()
和std::numeric_limits<int>::max()
实施范围检查。其余的是fmod
测试,考虑了fprem
指令(400828..40082d)的单次调用的所有不当行为以及NaN以某种方式出现的某些情况。
您可以使用remainder
获得类似的代码。
答案 3 :(得分:1)
要考虑的其他一些选项(不同的编译器/库可能会为这些测试产生不同的内部序列,并且更快/更慢):
bool is_int(float f) { return floor(f) == f; }
这是对你有溢出的测试的补充......
如果您希望真正优化,可以尝试以下方法(适用于正浮点数,未经过全面测试):这假定IEEE 32位浮点数,而不是C ++标准AFAIK强制要求。
bool is_int(float f)
{
const float nf = f + float(1 << 23);
const float bf = nf - float(1 << 23);
return f == bf;
}
答案 4 :(得分:1)
就我个人而言,我建议使用C ++ 11中引入的trunc
函数来检查f
是否为完整:
#include <cmath>
#include <type_traits>
template<typename F>
bool isIntegral(F f) {
static_assert(std::is_floating_point<F>::value, "The function isIntegral is only defined for floating-point types.");
return std::trunc(f) == f;
}
它不涉及任何铸造和没有浮点算术,这两者都可能是错误的来源。通过将尾数的相应位设置为零,至少在根据IEEE 754标准表示浮点值的情况下,可以在不引入数字误差的情况下完成小数位的截断。
就我个人而言,我会毫不犹豫地使用fmod
或remainder
来检查f
是否为完整,因为我不确定结果是否可以下溢到零,从而伪造一个整数值。在任何情况下,更容易显示trunc
无数值误差。
上述三种方法都没有实际检查浮点数f
是否可以表示为T
类型的值。需要额外检查。
第一个选项实际上就是这样:它检查f
是否为整数,并且可以表示为T
类型的值。它通过评估f == (T)f
来实现。这项检查涉及演员。根据C ++ 11标准第4.9节中的§1“如果截断值不能在目标类型中表示”,这种转换是未定义的。因此,如果f
是大于或等于std::numeric_limits<T>::max()+1
截断值肯定会导致未定义的行为。
这可能是为什么第一个选项在执行演员表之前有额外的范围检查(f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max()
)。此范围检查也可用于其他方法(trunc
,fmod
,remainder
),以确定f
是否可以表示为{{1}的值1}}。但是,检查有缺陷,因为它可能会遇到未定义的行为:
在此检查中,限制T
将转换为浮点类型以应用相等运算符。例如,如果std::numeric_limits<T>::min/max()
和T=uint32_t
为f
,则float
无法表示为浮点数。然后,C ++ 11标准在4.9§2节中规定,实现可以自由选择下一个更低或更高的可表示值。如果它选择更高的可表示值并且std::numeric_limits<T>::max()
恰好等于更高的可表示值,则根据4.9节中的§1来定义后续的强制转换,因为(截断的)值不能在目标类型中表示(uint32_t)
f
因此,第一个选项会确定std::cout << std::numeric_limits<uint32_t>::max() << std::endl; // 4294967295
std::cout << std::setprecision(20) << static_cast<float>(std::numeric_limits<uint32_t>::max()) << std::endl; // 4294967296 (float is a single precision IEEE 754 floating point number here)
std::cout << static_cast<uint32_t>(static_cast<float>(std::numeric_limits<uint32_t>::max())) << std::endl; // Could be for example 4294967295 due to undefined behavior according to the standard in the cast to the uint32_t.
是完整的,可表示为f
,即使它不是。{/ p>
一般来说,修复范围检查并不容易。根据标准,有符号整数和浮点数没有固定表示(例如二进制补码或IEEE 754)这一事实并不会使事情变得更容易。一种可能性是为您使用的特定编译器,体系结构和类型编写非可移植代码。更便携的解决方案是使用Boost的NumericConversion库:
uint32_t
然后你最终可以安全地执行施法:
#include <boost/numeric/conversion/cast.hpp>
template<typename T, typename F>
bool isRepresentableAs(F f) {
static_assert(std::is_floating_point<F>::value && std::is_integral<T>::value, "The function isRepresentableAs is only defined for floating-point as integral types.");
return boost::numeric::converter<T, F>::out_of_range(f) == boost::numeric::cInRange && isIntegral(f);
}
答案 5 :(得分:1)
我深入研究IEE 754标准,并且只考虑这种类型,我将假设64位整数和双精度。
数字是一个整数iff:
我做了以下功能:
#include <stdio.h>
int IsThisDoubleAnInt(double number)
{
long long ieee754 = *(long long *)&number;
long long sign = ieee754 >> 63;
long long exp = ((ieee754 >> 52) & 0x7FFLL);
long long mantissa = ieee754 & 0xFFFFFFFFFFFFFLL;
long long e = exp - 1023;
long long decimalmask = (1LL << (e + 52));
if (decimalmask) decimalmask -= 1;
if (((exp == 0) && (mantissa != 0)) || (e > 52) || (e < 0) || ((mantissa & decimalmask) != 0))
{
return 0;
}
else
{
return 1;
}
}
作为此功能的测试:
int main()
{
double x = 1;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 1.5;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 2;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 2.000000001;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 1e60;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 1e-60;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 1.0/0.0;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = x/x;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 0.99;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = 1LL << 52;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
x = (1LL << 52) + 1;
printf("x = %e is%s int.\n", x, IsThisDoubleAnInt(x)?"":" not");
}
结果如下:
x = 1.000000e+00 is int.
x = 1.500000e+00 is not int.
x = 2.000000e+00 is int.
x = 2.000000e+00 is not int.
x = 1.000000e+60 is not int.
x = 1.000000e-60 is not int.
x = inf is not int.
x = nan is not int.
x = 9.900000e-01 is not int.
x = 4.503600e+15 is int.
x = 4.503600e+15 is not int.
方法中的条件不是很清楚,因此我发布了带有注释if / else结构的较少混淆的版本。
int IsThisDoubleAnIntWithExplanation(double number)
{
long long ieee754 = *(long long *)&number;
long long sign = ieee754 >> 63;
long long exp = ((ieee754 >> 52) & 0x7FFLL);
long long mantissa = ieee754 & 0xFFFFFFFFFFFFFLL;
if (exp == 0)
{
if (mantissa == 0)
{
// This is signed zero.
return 1;
}
else
{
// this is a subnormal number
return 0;
}
}
else if (exp == 0x7FFL)
{
// it is infinity or nan.
return 0;
}
else
{
long long e = exp - 1023;
long long decimalmask = (1LL << (e + 52));
if (decimalmask) decimalmask -= 1;
printf("%f: %llx (%lld %lld %llx) %llx\n", number, ieee754, sign, e, mantissa, decimalmask);
// number is something in form (-1)^sign x 2^exp-1023 x 1.mantissa
if (e > 63)
{
// number too large to fit into integer
return 0;
}
else if (e > 52)
{
// number too large to have all digits...
return 0;
}
else if (e < 0)
{
// number too large to have all digits...
return 0;
}
else if ((mantissa & decimalmask) != 0)
{
// number has nonzero fraction part.
return 0;
}
}
return 1;
}
答案 6 :(得分:0)
以下是我要尝试的内容:
float originalNumber;
cin >> originalNumber;
int temp = (int) originalNumber;
if (originalNumber-temp > 0)
{
// It is not an integer
}
else
{
// It is an integer
}
答案 7 :(得分:0)
问题:
if ( f >= std::numeric_limits<T>::min()
&& f <= std::numeric_limits<T>::max()
&& f == (T)f))
如果T是(例如)64位,那么当转换为通常的64位双倍时,最大值将被舍入:-(假设2的补码,当然,min的情况也是如此。
所以,根据mantisaa中的位数和T中的位数,你需要屏蔽掉std :: numeric_limits :: max()的LS位......对不起,我不要做C ++,所以如何最好地做到这一点我留给别人。 [在C中它将是LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG)
的行 - 假设T是long long int
而f是double
并且这些都是通常的64位值。]
如果T是常数,那么min和max的两个浮点值的构造将(我假设)在编译时完成,因此这两个比较非常简单。你真的不需要能够浮动T ...但是你需要知道它的最小值和最大值将适合普通的整数(比如long long int)。
剩下的工作是将float转换为整数,然后再次浮动再次进行最终比较。因此,假设f在范围内(保证(T)f不溢出):
i = (T)f ; // or i = (long long int)f ;
ok = (i == f) ;
替代方案似乎是:
i = (T)f ; // or i = (long long int)f ;
ok = (floor(f) == f) ;
如其他地方所述。这取代了i
floor(f)
的浮动......我不相信这是一种改进。
如果f是NaN,可能会出错,所以你可能也想测试它。
您可以尝试使用f
解压缩frexp()
并将尾数提取为(比方说)一个long long int(使用ldexp()
和一个强制转换),但是当我开始将其绘制出来时它看起来很难看: - (
熟悉它,处理最大问题的一种更简单的方法是:min <= f < ((unsigned)max+1)
- 或min <= f < (unsigned)min
- 或(double)min <= f < -(double)min
- 或任何其他构建方法-2 ^(n-1)和+ 2 ^(n-1)作为浮点值,其中n是T中的位数。
(在凌晨1点对我的问题感兴趣!)
答案 8 :(得分:0)
如何转换这样的类型?
bool can_convert(float a, int i)
{
int b = a;
float c = i;
return a == c;
}
答案 9 :(得分:0)
首先,我想知道我的问题是否正确。根据我的阅读,似乎你想确定一个浮点实际上只是浮点数中一个整数类型的表示。
据我所知,由于浮点不准确,在浮点上执行==
是不安全的。因此,我提出以下解决方案,
template<typename F, typename I = size_t>
bool is_integral(F f)
{
return fabs(f - static_cast<I>(f)) <= std::numeric_limits<F>::epsilon;
}
我们的想法是简单地找到原始浮点和嵌入到整数类型的浮点之间的绝对差值,然后确定它是否小于浮点类型的epsilon。我在这里假设,如果它小于epsilon,那么差异对我们来说并不重要。
感谢您的阅读。
答案 10 :(得分:0)
使用modf()
将值分解为整数和小数部分。从这个直接测试中,可以知道double
是否是整数。在此之后,可以对目标整数类型的最小值/最大值进行限制测试。
#include <cmath>
bool IsInteger(double x) {
double ipart;
return std::modf(x, &ipart) == 0.0; // Test if fraction is 0.0.
}
注意modf()
与名为fmod()
的相似内容不同。
在OP发布的3个方法中,转换为/从整数转换可能会执行大量的工作并进行比较。其他2个略微相同。他们工作,假设没有意外的舍入模式效果除以1.0。但是做一些不必要的分歧。
哪个最快可能取决于所使用的double
的混合。
答案 11 :(得分:-1)
如果您的问题是“我可以将此双重转换为int而不会丢失信息吗?”然后我会做一些简单的事情:
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