Как проверить, может ли float быть точно представлен как целое число - PullRequest
15 голосов
/ 18 января 2012

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

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

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

Так как же я могу проверить, является ли двойное число точно представимымкак долго?

Спасибо

Ответы [ 6 ]

10 голосов
/ 18 января 2012

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

Вы могли бы:

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

Примерно так:

double val = ... ;  //  Value

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

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

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

А вот грязное решение с битовой маской:
Это использует объединение типов и предполагает двойную точность IEEE. Штукатурка типа Union действительна только в 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;
    }
}
9 голосов
/ 24 июля 2013

Я думаю, что нашел способ сжать 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 может привести к неопределенному поведению в этом случае.

Удивительно, но решение очень простое.Учтите, что хотя 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);
        }
    }
}
4 голосов
/ 15 января 2013

Вы можете использовать функцию 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;
    }
}
1 голос
/ 12 июля 2018

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

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

Диапазон (LONG_MIN, LONG_MAX) и дробные (frexp()) тесты.Также нужно остерегаться не-чисел.


Обычная идея - тестировать как (double)(long)x == x, но избегать его прямого использования.(long)x, когда x выходит за пределы диапазона, равен неопределенное поведение (UB).

Допустимый диапазон преобразования для (long)x равен LONG_MIN - 1 < x < LONG_MAX + 1, поскольку код отбрасывает любые дробныечасть x во время конвертации.Таким образом, код должен проверяться с использованием математики FP, если x находится в диапазоне.

#include <limits.h>
#include <stdbool.h>
#define DBL_LONG_MAXP1 (2.0*(LONG_MAX/2+1)) 
#define DBL_LONG_MINM1 (2.0*(LONG_MIN/2-1)) 

bool double_to_long_exact_possible(double x) {
  if (x < DBL_LONG_MAXP1) {
    double whole_number_part;
    if (frexp(x, &whole_number_part) != 0.0) {
      return false;  // Fractional part exist.
    }
    #if -LONG_MAX == LONG_MIN
    // rare non-2's complement machine 
    return x > DBL_LONG_MINM1;
    #else
    return x - LONG_MIN > -1.0;
    #endif 
  }
  return false;  // Too large or NaN
}
0 голосов
/ 05 сентября 2013

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

0 голосов
/ 18 января 2012

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

double val = ... ;  //  Value

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