Подтвердить что ты не робот

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

[Есть несколько вопросов по этому вопросу, но ни один из ответов не является особенно окончательным, а некоторые устарели от текущего стандарта С++].

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

4b9b3361

Ответ 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