Лучший способ проверить, является ли плавающая точка целым числом
[Есть несколько вопросов по этому вопросу, но ни один из ответов не является особенно окончательным, а некоторые устарели от текущего стандарта С++].
Мои исследования показывают, что это основные методы, используемые для проверки того, можно ли преобразовать значение с плавающей запятой в интегральный тип 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
, например.
С С++ 11, какой из них лучше? Есть ли лучший способ?
Ответы
Ответ 1
Используйте std::fmod(f, 1.0) == 0.0
, где f
является либо float
, double
, либо long double
. Если вы беспокоитесь о побочных эффектах нежелательных рекламных акций с плавающей запятой при использовании float
s, используйте либо 1.0f
, либо более всеобъемлющий
std::fmod(f, static_cast<decltype(f)>(1.0)) == 0.0
который заставит, очевидно, во время компиляции правильную перегрузку, которую нужно вызвать. Возвращаемое значение std::fmod(f, ...)
будет находиться в диапазоне [0, 1) и совершенно безопасно сравнивать с 0.0
, чтобы завершить проверку целых чисел.
Если окажется, что f
является целым числом, убедитесь, что оно находится в пределах допустимого диапазона выбранного вами типа, прежде чем пытаться выполнить бросок: иначе вы рискуете вызвать поведение undefined. Я вижу, что вы уже знакомы с std::numeric_limits
, который может вам помочь.
Мои оговорки против использования std::remainder
, возможно, (i) являются моим Luddite и (ii) он недоступен в некоторых компиляторах, частично реализующих стандарт С++ 11, такой как MSVC12. Мне не нравятся решения, связанные с приведениями, поскольку обозначение скрывает эту разумно дорогую операцию, и вам необходимо заранее проверить безопасность. Если вы должны принять свой первый выбор, по крайней мере, заменить C-стиль при помощи static_cast<T>(f)
;
Ответ 2
Заключение:
Ответ - использовать 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)
Таким образом, показатель степени в основном состоит из того, сколько двоичных цифр мы собираемся сдвинуть "двоичную точку" вниз по мантиссе. Положительное значение сдвигает его вправо, отрицательное значение сдвигает его влево. Если все цифры справа от двоичной точки равны нулю, то у нас есть целое число.
Если мы предположим, IEEE 754
Следует отметить, что в этом представлении значение нормализовано, так что старший значащий бит в мантиссе смещен на 1. Поскольку этот бит всегда установлен, он фактически не сохраняется (процессор знает его там и компенсирует соответственно).
Так:
Если exponent < 0
то у вас определенно нет целого числа, поскольку оно может представлять только дробное значение. Если exponent >= <Number of bits In Mantissa>
то здесь определенно нет фрактальной части, и она является целым числом (хотя вы не сможете удерживать ее в int
).
В противном случае мы должны сделать некоторую работу. если exponent >= 0 && exponent < <Number of bits In Mantissa>
в mantissa
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 (в этом простом тесте), кажется, превосходит метод усечения и генерирует тот же результат. НО количество времени незначительно. За 12 миллионов звонков мы увидели разницу в 14 миллисекунд.
Ответ 3
Этот тест хорош:
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
. Преобразования с плавающей точкой в интеграл, которые переполняют интегральный тип, приводят к поведению undefined, что даже хуже округления.
Я бы рекомендовал избегать 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;
}
компилирует (gcc версия 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 <[email protected]>
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
.
Ответ 4
Некоторые другие варианты для рассмотрения (разные компиляторы/библиотеки могут создавать разные встроенные последовательности для этих тестов и быть более быстрыми/медленными):
bool is_int(float f) { return floor(f) == f; }
Это дополнение к испытаниям для переполнения, которые у вас есть...
Если вы действительно хотите оптимизировать, вы можете попробовать следующее (работает для позитивных поплавков, а не тщательно протестировано): предполагается, что 32-битные поплавки IEEE, которые не предусмотрены стандартом С++ стандарта AFAIK.
bool is_int(float f)
{
const float nf = f + float(1 << 23);
const float bf = nf - float(1 << 23);
return f == bf;
}
Ответ 5
Лично я бы рекомендовал использовать функцию trunc
, введенную в С++ 11, чтобы проверить интеграл 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
. Эта проверка включает в себя актерский состав. Такое литье undefined согласно § 1 в разделе 4.9 стандарта С++ 11 "если усеченное значение не может быть представлено в типе назначения". Таким образом, если f
является, например, как правило, больше или равно std::numeric_limits<T>::max()+1
, урезанное значение, несомненно, будет иметь поведение undefined.
Вероятно, поэтому перед тем, как выполнить трансляцию, первая опция имеет дополнительную проверку диапазона (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max()
). Эта проверка диапазона также может использоваться для других методов (trunc
, fmod
, remainder
), чтобы определить, может ли f
быть представлено как значение типа T
. Однако проверка ошибочна, поскольку она может работать в режиме undefined:
В этой проверке пределы std::numeric_limits<T>::min/max()
преобразуются в тип с плавающей точкой для применения оператора равенства. Например, если T=uint32_t
и f
является float
, std::numeric_limits<T>::max()
не представляется в виде числа с плавающей запятой. Затем в стандарте С++ 11 говорится в разделе 4.9 § 2, что реализация может выбирать следующее нижнее или более высокое представляемое значение. Если он выбирает более высокое представляемое значение и f
оказывается равным более высокому представимому значению, последующее литье undefined согласно § 1 в разделе 4.9, поскольку (усеченное) значение не может быть представлено в целевом типе (uint32_t).
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
является интегральным и представляется как uint32_t
, хотя это не так.
Фиксация проверки диапазона в целом непростая. Тот факт, что целые числа со знаком и номерами с плавающей запятой не имеют фиксированного представления (например, двух дополнений или IEEE 754) в соответствии со стандартом, не упрощает работу. Одна из возможностей - написать непереносимый код для конкретного компилятора, архитектуры и типов, которые вы используете. Более портативное решение - использовать библиотеку Boost NumericConversion:
#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);
}
Затем вы можете, наконец, выполнить бросок:
double f = 333.0;
if (isRepresentableAs<uint32_t>(f))
std::cout << static_cast<uint32_t>(f) << std::endl;
else
std::cout << f << " is not representable as uint32_t." << std::endl;
// Output: 333
Ответ 6
Я бы углубился в стандарт IEE 754 и продолжал думать только в терминах этого типа, и я буду предполагать 64-битные целые числа и удвоения.
Число - это целое число iff:
- число равно нулю (независимо от знака).
- число имеет mantisa, не идущее на двоичные дроби (независимо от пения), не имея никаких undefined цифр для наименее значимых бит.
Я выполнил следующую функцию:
#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;
}
Ответ 7
Вот что я хотел бы попробовать:
float originalNumber;
cin >> originalNumber;
int temp = (int) originalNumber;
if (originalNumber-temp > 0)
{
// It is not an integer
}
else
{
// It is an integer
}
Ответ 8
Проблема с:
if ( f >= std::numeric_limits<T>::min()
&& f <= std::numeric_limits<T>::max()
&& f == (T)f))
заключается в том, что если T является (например) 64 битами, то max будет округлен при преобразовании в обычный 64-битный double:-( Предполагая, что 2 дополнения, то же самое не относится к min, конечно.
Итак, в зависимости от количества бит в mantisaa и количества бит в T, вам нужно замаскировать LS бит std:: numeric_limits:: max()... Извините, я не делайте С++, так как лучше всего это делать другим. [В C это было бы что-то в строках LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG)
- если T есть long long int
, а f - double
и что оба являются обычными 64-битными значениями.]
Если T является постоянным, то построение двух значений с плавающей запятой для min и max будет (я предполагаю) выполняться во время компиляции, поэтому два сравнения довольно просты. Вам действительно не нужно иметь возможность плавать T... но вам нужно знать, что его min и max будут вписываться в обычное целое число (long long int, скажем).
Оставшаяся работа преобразует float в integer, а затем плавает, снова создавая резервную копию для окончательного сравнения. Итак, если 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()
и извлечь мантиссу как (скажем) длинный длинный int (с ldexp()
и литой), но когда я начал набросать это, он выглядел уродливым:-(
Проспав на нем, более простой способ справиться с проблемой max - это сделать: min <= f < ((unsigned)max+1)
- или min <= f < (unsigned)min
- или (double)min <= f < -(double)min
- или любой другой метод построения -2 ^ (n- 1) и + 2 ^ (n-1) как значения с плавающей запятой, где n - количество бит в T.
(Служит мне для того, чтобы заинтересоваться проблемой в 1:00!)
Ответ 9
как насчет конвертирования таких типов?
bool can_convert(float a, int i)
{
int b = a;
float c = i;
return a == c;
}
Ответ 10
Прежде всего, я хочу посмотреть, правильно ли я получил ваш вопрос. Из того, что я прочитал, кажется, что вы хотите определить, является ли плавающая точка просто представлением целочисленного типа в плавающей точке.
Насколько мне известно, выполнение ==
в плавающей запятой небезопасно из-за неточностей с плавающей запятой. Поэтому я предлагаю следующее решение:
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, разница не имеет для нас никакого значения.
Спасибо, что прочитали.
Ответ 11
Используйте 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, приведение в/из целого числа может выполнять значительную работу, выполняющую приведение и сравнение. Остальные 2 незначительно одинаковы. Они работают, не предполагая неожиданных эффектов эффекта округления от деления на 1.0. Но сделайте ненужный разрыв.
Что наиболее вероятно, зависит от используемого сочетания double
.
Первый метод OP имеет исключительное преимущество: поскольку цель состоит в том, чтобы проверить, может ли FP точно преобразовать какое-то целое число, и, скорее всего, если результат будет истинным, тогда необходимо преобразование, первый метод OP уже сделан преобразование.
Ответ 12
Если ваш вопрос: "Могу ли я преобразовать этот двойник в 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