c++ - programacion - punto flotante metodos numericos
La mejor manera de verificar si un punto flotante es un entero (12)
[Hay algunas preguntas sobre esto pero ninguna de las respuestas es particularmente definitiva y varias están desactualizadas con el estándar actual de C ++].
Mi investigación muestra que estos son los principales métodos utilizados para verificar si un valor de punto flotante se puede convertir en un tipo T
integral.
if (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max() && f == (T)f))
usando
std::fmod
para extraer el resto y probar la igualdad a 0.usando
std::remainder
e igualdad de prueba a 0.
La primera prueba supone que se define una conversión de f
a una instancia de T
No es cierto para std::int64_t
para float
, por ejemplo.
Con C ++ 11, ¿cuál es el mejor? ¿Hay alguna manera mejor?
Conclusión:
La respuesta es usar std::trunc(f) == f
la diferencia de tiempo es insignificante cuando se comparan todos estos métodos. Incluso si el código específico de desenrollado de IEEE que escribimos en el siguiente ejemplo es técnicamente dos veces rápido, solo estamos hablando de 1 nano segundo más rápido.
Sin embargo, los costos de mantenimiento a largo plazo serían significativamente más altos. Por lo tanto, es mejor usar una solución que sea más fácil de leer y comprender para el mantenedor.
Tiempo en microsegundos para completar 12,000,000 operaciones en un conjunto aleatorio de números:
- Desglose 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
La elaboración de la conclusión.
Un número de punto flotante es dos partes:
mantissa: The data part of the value.
exponent: a power to multiply it by.
sutch that:
value = mantissa * (2^exponent)
Entonces, el exponente es básicamente cuántos dígitos binarios vamos a desplazar el "punto binario" hacia abajo de la mantisa. Un valor positivo lo desplaza hacia la derecha y un valor negativo lo desplaza hacia la izquierda. Si todos los dígitos a la derecha del punto binario son cero, entonces tenemos un entero.
Si asumimos IEEE 754
Debemos tener en cuenta que, en esta representación, el valor se normaliza de modo que el bit más significativo de la mantisa se desplaza a 1. Dado que este bit siempre se establece, no se almacena realmente (el procesador sabe que está allí y lo compensa en consecuencia).
Asi que:
Si el exponent < 0
entonces definitivamente no tiene un entero, ya que solo puede representar un valor fraccionario. Si el exponent >= <Number of bits In Mantissa>
entonces definitivamente no hay una parte fractual y es un entero (aunque es posible que no pueda mantenerlo en una hormiga).
De lo contrario tendremos que hacer algún trabajo. si el exponent >= 0 && exponent < <Number of bits In Mantissa>
entonces puede representar un número entero si la mantissa
es cero en la mitad inferior (definida abajo).
Adicional como parte de la normalización 127 se agrega al exponente (para que no haya valores negativos almacenados en el campo de exponente de 8 bits).
#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";
}
Resultados:
> ./a.out
16=> 1
16.4=> 0
123=> 1
0=> 1
2=> 1
4=> 1
5=> 1
1=> 1
Ejecutando algunas pruebas de tiempo.
Los datos de prueba se generaron de esta manera:
(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 () modificado para ejecutar pruebas:
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"; }
Las pruebas muestran:
> ./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
El código IEEE (en esta simple prueba) parece superar el método de truncado y generar el mismo resultado. PERO la cantidad de tiempo es insignificante. Más de 12 millones de llamadas vimos una diferencia en 14 milisegundos.
¿Qué pasa con la conversión de tipos como este?
bool can_convert(float a, int i)
{
int b = a;
float c = i;
return a == c;
}
Algunas otras opciones a considerar (diferentes compiladores / bibliotecas pueden producir diferentes secuencias intrínsecas para estas pruebas y ser más rápido / lento):
bool is_int(float f) { return floor(f) == f; }
Esto es además de las pruebas de desbordamiento que tiene ...
Si está buscando una optimización real, puede probar lo siguiente (funciona para flotadores positivos, no probados a fondo): Esto supone que los flotadores de 32 bits IEEE, que no están obligados por el estándar AFAIK de C ++.
bool is_int(float f)
{
const float nf = f + float(1 << 23);
const float bf = nf - float(1 << 23);
return f == bf;
}
El problema con:
if ( f >= std::numeric_limits<T>::min()
&& f <= std::numeric_limits<T>::max()
&& f == (T)f))
es que si T es (por ejemplo) 64 bits, entonces el máximo se redondeará cuando se convierta a su doble de 64 bits habitual :-( Suponiendo que el complemento de 2, lo mismo no es verdad del mínimo, por supuesto.
Entonces, dependiendo de la cantidad de bits en la mantisaa y la cantidad de bits en T, necesitas enmascarar los bits LS de std :: numeric_limits :: max () ... Lo siento, no lo hago hacer C ++, entonces como mejor hacer eso lo dejo a los demás. [En C sería algo LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG)
- suponiendo que T es long long int
yf es double
y que estos son los valores habituales de 64 bits.]
Si la T es constante, entonces la construcción de los dos valores de punto flotante para min y max se hará (supongo) en tiempo de compilación, por lo que las dos comparaciones son bastante sencillas. Realmente no necesita poder flotar T ... pero sí necesita saber que su mínimo y máximo cabrán en un entero normal (long long int, por ejemplo).
El trabajo restante es convertir el flotante en entero, y luego flotar de nuevo para la comparación final. Entonces, asumiendo que f está dentro del rango (lo que garantiza que (T) f no se desborda):
i = (T)f ; // or i = (long long int)f ;
ok = (i == f) ;
La alternativa parece ser:
i = (T)f ; // or i = (long long int)f ;
ok = (floor(f) == f) ;
como se señala en otra parte. Lo que reemplaza la flotación de i
por floor(f)
... que no estoy convencido es una mejora.
Si f es NaN, las cosas pueden salir mal, por lo que es posible que también desee realizar una prueba.
Puedes intentar desempaquetar f
con frexp()
y extraer la mantisa como (digamos) un largo int largo (con ldexp()
y un molde), pero cuando empecé a dibujar eso se veía feo :-(
Habiendo dormido en él, una forma más sencilla de lidiar con el problema máximo es hacer: min <= f < ((unsigned)max+1)
- o min <= f < (unsigned)min
- o (double)min <= f < -(double)min
- o cualquier otro método para construir -2 ^ (n-1) y + 2 ^ (n-1) como valores de punto flotante, donde n es el número de bits en T.
(¡Me sirve para interesarme en un problema a la 1:00 am!)
En primer lugar, quiero ver si entendí bien tu pregunta. Por lo que he leído, parece que desea determinar si un punto flotante es en realidad simplemente una representación de un tipo integral en punto flotante.
Por lo que sé, realizar ==
en un punto flotante no es seguro debido a imprecisiones del punto flotante. Por lo tanto estoy proponiendo la siguiente solución,
template<typename F, typename I = size_t>
bool is_integral(F f)
{
return fabs(f - static_cast<I>(f)) <= std::numeric_limits<F>::epsilon;
}
La idea es simplemente encontrar la diferencia absoluta entre el punto flotante original y el punto flotante convertido al tipo integral, y luego determinar si es más pequeño que el épsilon del tipo de punto flotante. Estoy asumiendo aquí que si es más pequeño que épsilon, la diferencia no es importante para nosotros.
Gracias por leer.
Esta prueba es buena:
if ( f >= std::numeric_limits<T>::min()
&& f <= std::numeric_limits<T>::max()
&& f == (T)f))
Estas pruebas son incompletas:
using std::fmod to extract the remainder and test equality to 0.
using std::remainder and test equality to 0.
Ambos fallan en verificar que la conversión a T
esté definida. Las conversiones de flotación a integral que desbordan el tipo integral dan como resultado un comportamiento indefinido, que es incluso peor que el redondeo.
Recomendaría evitar std::fmod
por otra razón. Este codigo
int isinteger(double d) {
return std::numeric_limits<int>::min() <= d
&& d <= std::numeric_limits<int>::max()
&& std::fmod(d, 1.0) == 0;
}
compila (gcc versión 4.9.1 20140903 (versión preliminar) (GCC) en x86_64 Arch Linux usando -g -O3 -std = gnu ++ 0x) para esto:
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
Las primeras cinco instrucciones implementan la verificación de rango contra std::numeric_limits<int>::min()
y std::numeric_limits<int>::max()
. El resto es la prueba fmod
, que explica todo el mal comportamiento de una sola invocación de la instrucción fprem (400828..40082d) y algún caso en el que surgió un NaN de alguna manera.
Obtienes un código similar usando el remainder
.
Esto es lo que intentaría:
float originalNumber;
cin >> originalNumber;
int temp = (int) originalNumber;
if (originalNumber-temp > 0)
{
// It is not an integer
}
else
{
// It is an integer
}
Me gustaría profundizar en el estándar IEE 754 y seguir pensando solo en términos de este tipo y asumiré enteros y dobles de 64 bits.
El número es un número entero iff:
- el número es cero (independientemente de la señal).
- el número tiene mantisa que no va a las fracciones binarias (independientemente de la canción), mientras que no tiene ningún dígito indefinido para los bits menos significativos.
Hice la siguiente función:
#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;
}
}
Como prueba de esta función:
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");
}
El resultado es el siguiente:
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.
La condición en el método no es muy clara, por lo tanto, estoy publicando la versión menos ofuscada con una estructura comentada 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;
}
Personalmente, recomendaría usar la función trunc
introducida en C ++ 11 para verificar si f
es integral:
#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;
}
No implica fundición ni aritmética de punto flotante, las cuales pueden ser una fuente de error. El truncamiento de los lugares decimales seguramente se puede hacer sin introducir un error numérico estableciendo los bits correspondientes de la mantisa a cero al menos si los valores de punto flotante están representados de acuerdo con el estándar IEEE 754.
Personalmente, dudaría en usar fmod
o el remainder
para verificar si f
es integral porque no estoy seguro de que el resultado pueda llegar a cero y, por lo tanto, falsificar un valor integral. En cualquier caso, es más fácil demostrar que trunc
funciona sin error numérico.
Ninguno de los tres métodos anteriores realmente verifica si el número de punto flotante f
se puede representar como un valor de tipo T
Es necesario un control adicional.
La primera opción realmente hace exactamente eso: verifica si f
es integral y se puede representar como un valor de tipo T
Lo hace evaluando f == (T)f
. Esta comprobación implica un reparto. Dicha conversión no está definida según §1 en la sección 4.9 de la norma C ++ 11 "si el valor truncado no puede representarse en el tipo de destino". Por lo tanto, si f
es, por ejemplo, mayor o igual que std::numeric_limits<T>::max()+1
el valor truncado tendrá un comportamiento indefinido como consecuencia.
Probablemente sea por eso que la primera opción tiene una verificación de rango adicional ( f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max()
) antes de realizar la conversión. Esta verificación de rango también podría usarse para los otros métodos ( trunc
, fmod
, remainder
) para determinar si f
puede representarse como un valor de tipo T
Sin embargo, la verificación es defectuosa, ya que puede tener un comportamiento indefinido: en esta verificación, los límites std::numeric_limits<T>::min/max()
se convierten al tipo de punto flotante para aplicar el operador de igualdad. Por ejemplo, si T=uint32_t
y f
es un float
, std::numeric_limits<T>::max()
no se puede representar como un número de punto flotante. El estándar C ++ 11 establece en la sección 4.9 §2 que la implementación es libre de elegir el siguiente valor representable más bajo o más alto. Si elige el valor representable más alto y f
resulta ser igual al valor representable más alto, la conversión posterior no está definida según §1 en la sección 4.9, ya que el valor (truncado) no se puede representar en el tipo de destino (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.
En consecuencia, la primera opción establecería que f
es integral y representable como uint32_t
aunque no lo sea.
Reparar el control de rango en general no es fácil. El hecho de que los enteros con signo y los números de punto flotante no tengan una representación fija (como el complemento de dos o IEEE 754) de acuerdo con el estándar no facilita las cosas. Una posibilidad es escribir código no portátil para el compilador, la arquitectura y los tipos específicos que usa. Una solución más portátil es usar la biblioteca NumericConversion de Boost:
#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);
}
Entonces finalmente puedes realizar el reparto de forma segura:
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
Si su pregunta es "¿Puedo convertir este doble a int sin pérdida de información?" Entonces haría algo simple como:
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
Use modf()
que divide el valor en partes integrales y fraccionarias. De esta prueba directa, se sabe si el double
es un número entero o no. Después de esto, se pueden realizar pruebas de límite contra el mínimo / máximo del tipo entero objetivo.
#include <cmath>
bool IsInteger(double x) {
double ipart;
return std::modf(x, &ipart) == 0.0; // Test if fraction is 0.0.
}
Nota modf()
difiere del fmod()
similar llamado.
De los 3 métodos OP publicados, el lanzamiento a / desde un número entero puede realizar una buena cantidad de trabajo haciendo los lanzamientos y comparar. Los otros 2 son marginalmente iguales. Funcionan, asumiendo que no hay efectos de modo de redondeo inesperados de dividir por 1.0. Pero haz una división innecesaria.
En cuanto a cuál es el más rápido, es probable que dependa de la combinación del double
s utilizado.
El primer método de OP tiene una ventaja singular: ya que el objetivo es probar si un FP se puede convertir exactamente a un número entero, y probablemente si el resultado es verdadero, la conversión debe ocurrir, el primer método de OP ya realizó la conversión.
Use std::fmod(f, 1.0) == 0.0
donde f
es un float
, double
o long double
. Si está preocupado por los efectos espurios de las promociones de punto flotante no deseadas cuando usa float
s, entonces use 1.0f
o el más completo
std::fmod(f, static_cast<decltype(f)>(1.0)) == 0.0
lo que forzará, obviamente en el momento de la compilación, la sobrecarga correcta a ser llamada. El valor de retorno de std::fmod(f, ...)
estará en el rango [0, 1) y es perfectamente seguro comparar con 0.0
para completar su comprobación de enteros.
Si resulta que f
es un entero, entonces asegúrese de que esté dentro del rango permitido de su tipo elegido antes de intentar un lanzamiento: de lo contrario, se arriesga a invocar un comportamiento indefinido . Veo que ya estás familiarizado con std::numeric_limits
que puede ayudarte aquí.
Mis reservas contra el uso de std::remainder
son posiblemente (i) siendo un Luddite y (ii) no esté disponible en algunos compiladores que implementan parcialmente el estándar C ++ 11, como MSVC12. No me gustan las soluciones que involucran fundidos, ya que la notación oculta esa operación razonablemente costosa y debe verificar con anticipación la seguridad. Si debe adoptar su primera opción, al menos reemplace el modelo de estilo C con static_cast<T>(f)
;