c++ - selectiva - diferencia entre comillas simples y dobles en c
¿Se garantiza la conservación de un flotador cuando se transporta a través de un doble en C/C++? (3)
Suponiendo la conformidad IEEE-754 , ¿se garantiza que se mantendrá flotante cuando se transporte a través de un doble?
En otras palabras, ¿la siguiente afirmación siempre estará satisfecha?
int main()
{
float f = some_random_float();
assert(f == (float)(double)f);
}
Supongamos que f
podría adquirir cualquiera de los valores especiales definidos por IEEE, como NaN e Infinity.
Según IEEE, ¿hay algún caso en el que se cumpla la afirmación, pero la representación exacta a nivel de bit no se conserva después del transporte a través del doble?
El fragmento de código es válido tanto en C como en C ++.
De C99:
6.3.1.5 Tipos flotantes reales
1 Cuando un flotador se promueve a doble o doble largo, o un doble se promociona a doble largo, su valor no se modifica.
2 Cuando un doble se degrada para flotar, un doble largo se degrada a doble o flotante, o un valor que se representa con mayor precisión y rango que el requerido por su tipo semántico (ver 6.3.1.8) se convierte explícitamente a su tipo semántico, si el valor que se está convirtiendo se puede representar exactamente en el nuevo tipo, no se modifica ...
Creo que esto garantiza que una conversión float-> double-> float preservará el valor original de float.
El estándar también define las macros INFINITY
y NAN
en 7.12 Mathematics <math.h>
:
4 El macro INFINITY se expande a una expresión constante de tipo float que representa infinito positivo o sin signo, si está disponible; else a una constante positiva de tipo flotante que se desborda en el tiempo de traducción.
5 La macro NAN se define si y solo si la implementación admite NaN silenciosos para el tipo de flotante. Se expande a una expresión constante de tipo flotante que representa un NaN silencioso.
Por lo tanto, hay una disposición para dichos valores especiales y las conversiones también pueden funcionar para ellos (incluso para el infinito menos y el cero negativo).
La aserción fallará en el modo de borrado a cero y / o desnormalizado-es-cero (por ejemplo, código compilado con -mfpmath = sse, -fast-math, etc., pero también en montones de compiladores y arquitecturas por defecto, como el de Intel Compilador de C ++) si f se desnormaliza.
Sin embargo, no puede producir un flotador desnormalizado en ese modo, pero el escenario aún es posible:
a) El flotador desnormalizado proviene de una fuente externa.
b) Algunas bibliotecas alteran los modos de FPU pero olvidan (o evitan intencionalmente) volver a configurarlos después de cada llamada de función, lo que hace posible que la persona que llama no coincida con la normalización.
Ejemplo práctico que imprime a continuación:
f = 5.87747e-39
f2 = 5.87747e-39
f = 5.87747e-39
f2 = 0
error, f != f2!
El ejemplo funciona tanto para VC2010 como para GCC 4.3, pero supone que VC usa SSE para matemáticas como valor predeterminado y GCC usa FPU para matemáticas como valor predeterminado. El ejemplo puede no ilustrar el problema de otra manera.
#include <limits>
#include <iostream>
#include <cmath>
#ifdef _MSC_VER
#include <xmmintrin.h>
#endif
template <class T>bool normal(T t)
{
return (t != 0 || fabsf( t ) >= std::numeric_limits<T>::min());
}
void csr_flush_to_zero()
{
#ifdef _MSC_VER
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
#else
unsigned csr = __builtin_ia32_stmxcsr();
csr |= (1 << 15);
__builtin_ia32_ldmxcsr(csr);
#endif
}
void test_cast(float f)
{
std::cout << "f = " << f << "/n";
double d = double(f);
float f2 = float(d);
std::cout << "f2 = " << f2 << "/n";
if(f != f2)
std::cout << "error, f != f2!/n";
std::cout << "/n";
}
int main()
{
float f = std::numeric_limits<float>::min() / 2.0;
test_cast(f);
csr_flush_to_zero();
test_cast(f);
}
Ni siquiera necesitas asumir IEEE. C89 dice en 3.1.2.5:
El conjunto de valores del tipo
float
es un subconjunto del conjunto de valores del tipodouble
Y cualquier otro estándar de C y C ++ dice cosas equivalentes. Hasta donde sé, NaNs e infinities son "valores del tipo float
", aunque con valores con algunas reglas de casos especiales cuando se usan como operandos.
El hecho de que la conversión float -> double -> float restablece el valor original de float
sigue (en general) del hecho de que todas las conversiones numéricas conservan el valor si es representable en el tipo de destino.
Las representaciones a nivel de bit son una cuestión ligeramente diferente. Imagina que hay un valor de float
que tiene dos representaciones en bitwise distintas. Entonces, nada en el estándar C evita que la conversión flotante -> doble -> flotante cambie de una a la otra. En IEEE eso no sucederá con los "valores reales" a menos que haya bits de relleno, pero no sé si IEEE descarta que un único NaN tenga distintas representaciones bit a bit. Los NaNs no se comparan igual a ellos de todos modos, por lo que tampoco hay una forma estándar de decir si dos NaN son "el mismo NaN" o "NaN diferentes" que no sea la conversión a cadenas. El problema puede ser discutible.
Una cosa a tener en cuenta es a los modos de compilación no conformes, en los que mantienen los valores superprecisos "ocultos", por ejemplo, resultados intermedios que quedan en los registros de coma flotante y reutilizados sin redondeo. No creo que eso haga que tu código de ejemplo falle, pero tan pronto como estés haciendo floating-point ==
es el tipo de cosa por la que empiezas a preocuparte.