Лучший способ проверить, является ли число с плавающей точкой целым числом

[Есть несколько вопросов по этому вопросу, но ни один из ответов не является окончательным, и некоторые из них не соответствуют текущему стандарту 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, какой из них лучше? Есть ли способ лучше?

12 ответов

Решение

Использование 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);

Заключение:

Ответ использовать 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.

sutch that:

   value =  mantissa * (2^exponent)

Таким образом, показатель степени в основном состоит из того, сколько двоичных цифр мы собираемся сдвинуть "двоичную точку" вниз по мантиссе. Положительное значение сдвигает его вправо, отрицательное значение сдвигает его влево. Если все цифры справа от двоичной точки равны нулю, то у нас есть целое число.

Если мы предположим, IEEE 754

Следует отметить, что в этом представлении значение нормализовано, так что старший значащий бит в мантиссе смещен на 1. Поскольку этот бит всегда установлен, он фактически не сохраняется (процессор знает его там и компенсирует соответственно).

Так:

Если exponent < 0 тогда у вас точно нет целого числа, поскольку оно может представлять только дробное значение. Если exponent >= <Number of bits In 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 милли секунд.

Лично я бы рекомендовал использовать trunc функция введена в C++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, Эта проверка включает в себя приведение. Такое приведение не определено в соответствии с §1 в разделе 4.9 стандарта C++11 "если усеченное значение не может быть представлено в типе назначения". Таким образом, если f Например, больше или равно std::numeric_limits<T>::max()+1 как следствие, усеченное значение будет иметь неопределенное поведение.

Вероятно, поэтому первый вариант имеет дополнительную проверку диапазона (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max()) перед исполнением. Эта проверка диапазона может также использоваться для других методов (trunc, fmod, remainder) чтобы определить, f может быть представлен как значение типа T, Однако проверка имеет недостатки, поскольку может привести к неопределенному поведению: в этой проверке ограничения std::numeric_limits<T>::min/max() преобразовать в тип с плавающей точкой для применения оператора равенства. Например, если T=uint32_t а также f быть float, std::numeric_limits<T>::max() не представляется в виде числа с плавающей запятой. Стандарт C++11 затем утверждает в разделе 4.9 §2, что реализация свободна в выборе следующего более низкого или более высокого представимого значения. Если он выбирает более высокое представимое значение и f в случае совпадения с более высоким представимым значением, последующее приведение не определено в соответствии с §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's 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

Этот тест хорош:

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;
}

компилирует (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 <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,

Некоторые другие варианты для рассмотрения (разные компиляторы / библиотеки могут создавать разные внутренние последовательности для этих тестов и быть быстрее / медленнее):

bool is_int(float f) { return floor(f) == f; }

Это в дополнение к тестам на переполнение у вас...

Если вы хотите по-настоящему оптимизировать, вы можете попробовать следующее (работает для положительных операций с плавающей запятой, без тщательного тестирования): Это предполагает 32-разрядные операции с плавающей запятой IEEE, которые не предусмотрены стандартом AFAIK C++.

bool is_int(float f)
{
    const float nf = f + float(1 << 23);
    const float bf = nf - float(1 << 23);
    return f == bf;
}

Я бы углубился в стандарт IEE 754 и продолжал думать только с точки зрения этого типа, и я буду принимать 64-битные целые и двойные числа.

Число является целым числом, если:

  1. число ноль (независимо от знака).
  2. число имеет mantisa, не идущий в двоичные дроби (независимо от пения), в то же время не имея никаких неопределенных цифр для младших значащих бит.

Я сделал следующую функцию:

#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;
}

Вот что я бы попробовал:

float originalNumber;
cin >> originalNumber;
int temp = (int) originalNumber;
if (originalNumber-temp > 0)
{
    // It is not an integer
}
else
{
    // It is an integer
}

Проблема с:

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

в том, что если T равно (например) 64 битам, то при преобразовании в ваш обычный 64-битный двойной код будет округлено максимальное значение:-(Если допустить, что дополняет 2, то, конечно, то же самое не верно для min.

Таким образом, в зависимости от количества бит в mantisaa и количества бит в T, вам нужно замаскировать LS-биты std::numeric_limits::max()... извините, я не делать C++, так как лучше всего это оставить другим. [В C это было бы что-то вроде LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG) - при условии, что Т long long int и е это double и что это оба обычные 64-битные значения.]

Если T является константой, то построение двух значений с плавающей запятой для min и max будет (я полагаю) выполнено во время компиляции, поэтому два сравнения довольно просты. Вам не нужно иметь возможность плавать T... но вам нужно знать, что его min и max будут соответствовать обычному целому числу (скажем, long long int).

Оставшаяся работа заключается в преобразовании числа с плавающей точкой в ​​целое, а затем снова с плавающей запятой для окончательного сравнения. Итак, предполагая, что 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!)

Прежде всего, я хочу посмотреть, правильно ли я понял ваш вопрос. Из того, что я прочитал, кажется, что вы хотите определить, является ли плавающая точка на самом деле просто представлением целочисленного типа в плавающей точке.

Насколько я знаю, выполняя == на плавающей точке не является безопасным из-за неточностей с плавающей точкой. Поэтому я предлагаю следующее решение,

template<typename F, typename I = size_t>
bool is_integral(F f)
{
  return fabs(f - static_cast<I>(f)) <= std::numeric_limits<F>::epsilon;
}

Идея состоит в том, чтобы просто найти абсолютную разницу между исходной плавающей точкой и плавающей точкой, приведенной к целочисленному типу, а затем определить, меньше ли она эпсилона типа с плавающей точкой. Я предполагаю, что если он меньше эпсилона, разница для нас не имеет значения.

Спасибо за чтение.

Использование 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. Но делайте ненужный разрыв.

Что, скорее всего, зависит от сочетания doubleS используется.

Первый метод OP имеет особое преимущество: так как цель состоит в том, чтобы проверить, может ли FP преобразовать точно в некоторое целое число, и, вероятно, тогда, если результат верен, преобразование должно произойти, первый метод OP уже выполнил преобразование.

Как насчет преобразования типов, как это?

bool can_convert(float a, int i)
{
  int b = a;
  float c = i;
  return a == c;
}

Если ваш вопрос "Могу ли я преобразовать это число в 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
Другие вопросы по тегам