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

Как проверить, может ли float быть точно представлен как целое число

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

Моя первоначальная мысль состояла в том, чтобы проверить экспонента, чтобы увидеть, было ли это 0 (точнее, 127). Но это не сработает, потому что 2.0 будет e = 1 m = 1...

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

Итак, как я могу проверить, является ли double точно представимым как long?

Спасибо

4b9b3361

Ответ 1

Вот один из способов, который мог бы работать в большинстве случаев. Я не уверен, если/как он сломается, если вы дадите ему NaN, INF, очень большие (переполненные) номера...
(Хотя я думаю, что все они вернут false - не точно представимы.)

Вы можете:

  • Передайте его целому числу.
  • Верните его обратно в плавающую точку.
  • Сравните с исходным значением.

Что-то вроде этого:

double val = ... ;  //  Value

if ((double)(long long)val == val){
    //  Exactly representable
}

floor() и ceil() также являются честной игрой (хотя они могут выйти из строя, если значение переполняет целое число):

floor(val) == val
ceil(val) == val

И вот грязное решение для бит-масок:
Это использует тип union-punning и предполагает двойную точность IEEE. Тип union-punning действует только в C99 TR2 и более поздних версиях.

int representable(double x){
    //  Handle corner cases:
    if (x == 0)
      return 1;

    //  -2^63 is representable as a signed 64-bit integer, but +2^63 is not.
    if (x == -9223372036854775808.)
      return 1;

    //  Warning: Union type-punning is only valid in C99 TR2 or later.
    union{
        double f;
        uint64_t i;
    } val;

    val.f = x;

    uint64_t exp = val.i & 0x7ff0000000000000ull;
    uint64_t man = val.i & 0x000fffffffffffffull;
    man |= 0x0010000000000000ull;  //  Implicit leading 1-bit.

    int shift = (exp >> 52) - 1075;
    //  Out of range
    if (shift < -52 || shift > 10)
        return 0;

    //  Test mantissa
    if (shift < 0){
        shift = -shift;
        return ((man >> shift) << shift) == man;
    }else{
        return ((man << shift) >> shift) == man;
    }
}

Ответ 2

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

// INCORRECT CODE
uint64_t double_to_uint64 (double x)
{
    if (x < 0.0) {
        return 0;
    }
    if (x > UINT64_MAX) {
        return UINT64_MAX;
    }
    return x;
}

Проблема заключается в том, что во втором сравнении UINT64_MAX неявно преобразуется в double. В стандарте C не указывается точно, как это преобразование работает, только чтобы оно округлялось или уменьшалось до представляемого значения. Это означает, что второе сравнение может быть ложным, даже если оно должно быть математически истинным (что может произойти при округлении UINT64_MAX, а "x" - математически между UINT64_MAX и (double)UINT64_MAX). Таким образом, преобразование double в uint64_t может привести к поведению undefined в этом случае края.

Удивительно, но решение очень просто. Предположим, что хотя UINT64_MAX не может быть точно представимо в double, UINT64_MAX+1, будучи степенью двух (и не слишком больших), безусловно, есть. Итак, если мы сначала округлим ввод до целого числа, сравнение x > UINT64_MAX эквивалентно x >= UINT64_MAX+1, за исключением возможного переполнения в добавлении. Мы можем исправить переполнение, используя ldexp вместо того, чтобы добавить один к UINT64_MAX. При этом следующий код должен быть правильным.

/* Input: a double 'x', which must not be NaN.
 * Output: If 'x' is lesser than zero, then zero;
 *         otherwise, if 'x' is greater than UINT64_MAX, then UINT64_MAX;
 *         otherwise, 'x', rounded down to an integer.
 */
uint64_t double_to_uint64 (double x)
{
    assert(!isnan(x));
    double y = floor(x);
    if (y < 0.0) {
        return 0;
    }
    if (y >= ldexp(1.0, 64)) {
        return UINT64_MAX;
    }
    return y;
}

Теперь, чтобы вернуться к вашему вопросу: x точно представлен в uint64_t? Только если он не был ни округлен, ни зажат.

/* Input: a double 'x', which must not be NaN.
 * Output: If 'x' is exactly representable in an uint64_t,
 *         then 1, otherwise 0.
 */
int double_representable_in_uint64 (double x)
{
    assert(!isnan(x));
    return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 64));
}

Тот же алгоритм может использоваться для целых чисел разного размера, а также для целых чисел со знаком с незначительной модификацией. Следующий код делает некоторые очень простые тесты версий uint32_t и uint64_t (могут быть обнаружены только ложные срабатывания), но также подходит для ручного изучения случаев кромок.

#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include <assert.h>
#include <stdio.h>

uint32_t double_to_uint32 (double x)
{
    assert(!isnan(x));
    double y = floor(x);
    if (y < 0.0) {
        return 0;
    }
    if (y >= ldexp(1.0, 32)) {
        return UINT32_MAX;
    }
    return y;
}

uint64_t double_to_uint64 (double x)
{
    assert(!isnan(x));
    double y = floor(x);
    if (y < 0.0) {
        return 0;
    }
    if (y >= ldexp(1.0, 64)) {
        return UINT64_MAX;
    }
    return y;
}

int double_representable_in_uint32 (double x)
{
    assert(!isnan(x));
    return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 32));
}

int double_representable_in_uint64 (double x)
{
    assert(!isnan(x));
    return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 64));
}

int main ()
{
    {
        printf("Testing 32-bit\n");
        for (double x = 4294967295.999990; x < 4294967296.000017; x = nextafter(x, INFINITY)) {
            uint32_t y = double_to_uint32(x);
            int representable = double_representable_in_uint32(x);
            printf("%f -> %" PRIu32 " representable=%d\n", x, y, representable);
            assert(!representable || (double)(uint32_t)x == x);
        }
    }
    {
        printf("Testing 64-bit\n");
        double x = ldexp(1.0, 64) - 40000.0;
        for (double x = 18446744073709510656.0; x < 18446744073709629440.0; x = nextafter(x, INFINITY)) {
            uint64_t y = double_to_uint64(x);
            int representable = double_representable_in_uint64(x);
            printf("%f -> %" PRIu64 " representable=%d\n", x, y, representable);
            assert(!representable || (double)(uint64_t)x == x);
        }
    }
}

Ответ 3

Вы можете использовать функцию modf для разделения поплавка на части целых чисел и частей. modf находится в стандартной библиотеке C.

#include <math.h>
#include <limits.h>   

double val = ...
double i;
long l;

/* check if fractional part is 0 */
if (modf(val, &i) == 0.0) {
    /* val is an integer. check if it can be stored in a long */
    if (val >= LONG_MIN && val <= LONG_MAX) {
        /* can be exactly represented by a long */
        l = val;
    }
}

Ответ 4

Любое значение IEEE с плавающей запятой double или float с величиной, равной или превышающей 2 ^ 52 или 2 ^ 23, будет целым числом. Добавляя 2 ^ 52 или 2 ^ 23 к положительному числу, величина которого меньше, чем это приведет к округлению до целого числа. Вычитание добавленного значения даст целое число, которое будет равно оригиналу, если оригинал был целым числом. Обратите внимание, что этот алгоритм не сработает с некоторыми номерами, большими, чем 2 ^ 52, но он не нужен для больших чисел.

Ответ 5

Не могли бы вы использовать оператор модуля, чтобы проверить, является ли double делимым на один... или я полностью недопонимаю вопрос?

double val = ... ;  //  Value

if(val % 1 == 0) {
    // Val is evenly divisible by 1 and is therefore a whole number
}