Как проверить, может ли float быть точно представлен как целое число
Я ищу достаточно эффективный способ определения значения с плавающей запятой (double
) может быть точно представлен целочисленным типом данных (long
, 64 бит).
моя первоначальная мысль состояла в том, чтобы проверить показатель, чтобы увидеть, было ли это 0
(точнее 127
). Но это не сработает, потому что 2.0
будет e=1 m=1...
Итак, я застрял. У меня есть чувство, что я могу сделать это с помощью бит-масок, но я просто не понимаю, как сделать это в данный момент.
Итак, как я могу проверить, является ли двойной точно представимым как длинный?
спасибо
6 ответов
вот один метод, который может работать в большинстве случаев. Я не уверен, что/как он сломается, если вы его дадите NaN
, INF
, очень большие (переполнение) чисел...
(хотя я думаю, что все они вернут false - не совсем представимо.)
вы можете:
- приведите его к целому числу.
- верните его в плавающую точку.
- сравнить с исходным значением.
что-то вроде это:
double val = ... ; // Value
if ((double)(long long)val == val){
// Exactly representable
}
floor()
и ceil()
также справедливая игра (хотя они могут потерпеть неудачу, если значение переполняет целое число):
floor(val) == val
ceil(val) == val
и вот грязный бит-маска решение:
Это использует тип объединения-каламбур и предполагает двойную точность IEEE. Union type-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;
}
}
я думаю, что нашел способ зажать 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 не определяет точно, как это преобразование работает, только то, что оно должно быть округлено вверх или вниз до a представимое значение. Это означает, что второе сравнение может быть ложным, даже если математически должно быть истинным (что может произойти, когда UINT64_MAX
округляется, а " x " математически находится между UINT64_MAX
и (double)UINT64_MAX
). Таким образом, преобразование double
до uint64_t
может привести к неопределенному поведению в этом случае edge.
Удивительно, но решение очень простое. Считайте, что пока 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;
}
теперь вернемся к вашему вопросу: is 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);
}
}
}
вы можете использовать функцию 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;
}
}
Как проверить, может ли float быть точно представлен как целое число ?
Я ищу разумно эффективный способ определения, если значение с плавающей запятой
double
может быть точно представлен целочисленным типом данныхlong
, 64 бит.
любой IEEE с плавающей запятой double
или float
значение с величиной На или выше 2^52 или 2^23 будет целым числом. Добавление 2^52 или 2^23 к положительному числу, величина которого меньше, приведет к округлению его до целого числа. Вычитание добавленного значения даст целое число, которое будет равно исходному, если исходное было целым числом. Обратите внимание, что этот алгоритм потерпит неудачу с некоторыми числами больше 2^52, но он не нужен для чисел, которые большой.
Не могли бы вы использовать оператор модуля, чтобы проверить, делится ли двойник на единицу... или я совершенно неправильно понял вопрос?
double val = ... ; // Value
if(val % 1 == 0) {
// Val is evenly divisible by 1 and is therefore a whole number
}