Si tengo la siguiente declaración:
float a = 3.0 ;
¿Eso es un error? Leí en un libro que 3.0
es un double
valor y que tengo que especificarlo float a = 3.0f
. ¿Es tan?
Si tengo la siguiente declaración:
float a = 3.0 ;
¿Eso es un error? Leí en un libro que 3.0
es un double
valor y que tengo que especificarlo float a = 3.0f
. ¿Es tan?
;
después.
Respuestas:
No es un error declararlo float a = 3.0
: si lo hace, el compilador convertirá el doble literal 3.0 en un flotante por usted.
Sin embargo, debe usar la notación de literales flotantes en escenarios específicos.
Por motivos de rendimiento:
Específicamente, considere:
float foo(float x) { return x * 0.42; }
Aquí el compilador emitirá una conversión (que pagará en tiempo de ejecución) por cada valor devuelto. Para evitarlo debes declarar:
float foo(float x) { return x * 0.42f; } // OK, no conversion required
Para evitar errores al comparar resultados:
por ejemplo, la siguiente comparación falla:
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
Podemos arreglarlo con la notación literal flotante:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
(Nota: por supuesto, así no es como debe comparar números flotantes o dobles para la igualdad en general )
Para llamar a la función sobrecargada correcta (por la misma razón):
Ejemplo:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
int main()
{
foo(42.0); // calls double overload
foo(42.0f); // calls float overload
return 0;
}
Como señaló Cyber , en un contexto de deducción de tipo, es necesario ayudar al compilador a deducir float
:
En caso de auto
:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Y de manera similar, en caso de deducción por tipo de plantilla:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
template<typename T>
void bar(T t)
{
foo(t);
}
int main()
{
bar(42.0); // Deduce double
bar(42.0f); // Deduce float
return 0;
}
42
hay un número entero, que se promueve automáticamente a float
(y sucederá en tiempo de compilación en cualquier compilador decente), por lo que no hay ninguna penalización de rendimiento. Probablemente quisiste decir algo como 42.0
.
4.2
a 4.2f
puede tener el efecto secundario de establecer el FE_INEXACT
indicador, según el compilador y el sistema, y algunos (ciertamente pocos) programas se preocupan por qué operaciones de punto flotante son exactas y cuáles no, y prueban ese indicador . Esto significa que la simple transformación obvia en tiempo de compilación altera el comportamiento del programa.
float foo(float x) { return x*42.0; }
se puede compilar en una multiplicación de precisión simple, y fue compilado por Clang la última vez que lo intenté. Sin embargo float foo(float x) { return x*0.1; }
, no se puede compilar en una sola multiplicación de precisión simple. Puede haber sido un poco demasiado optimista antes de este parche, pero después del parche solo debería combinar conversion-double_precision_op-conversion con single_precision_op cuando el resultado sea siempre el mismo. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
someFloat
, la expresión someFloat * 0.1
dará resultados más precisos que someFloat * 0.1f
, aunque en muchos casos es más barata que una división de punto flotante. Por ejemplo, (float) (167772208.0f * 0.1) redondeará correctamente a 16777220 en lugar de 16777222. Algunos compiladores pueden sustituir una double
multiplicación por una división de coma flotante, pero para aquellos que no lo hacen (es seguro para muchos, aunque no para todos los valores ) la multiplicación puede ser una optimización útil, pero solo si se realiza con un double
recíproco.
El compilador convertirá cualquiera de los siguientes literales en flotantes, porque usted declaró la variable como flotante.
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
Sería importante si utilizó auto
(u otro tipo de métodos de deducción), por ejemplo:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
auto
lo que no es el único caso.
Los literales de coma flotante sin sufijo son de tipo double , esto se trata en la sección del estándar de C ++ del borrador 2.14.4
Literales flotantes :
[...] El tipo de un literal flotante es doble a menos que se especifique explícitamente con un sufijo. [...]
entonces, ¿es un error asignar 3.0
un doble literal a un flotante ?:
float a = 3.0
No, no lo es, se convertirá, lo cual se trata en la sección 4.8
Conversiones de punto flotante :
Un prvalue de tipo de coma flotante se puede convertir en un prvalue de otro tipo de coma flotante. Si el valor de origen se puede representar exactamente en el tipo de destino, el resultado de la conversión es esa representación exacta. Si el valor de origen está entre dos valores de destino adyacentes, el resultado de la conversión es una elección definida por la implementación de cualquiera de esos valores. De lo contrario, el comportamiento no está definido.
Podemos leer más detalles sobre las implicaciones de esto en GotW # 67: doble o nada que dice:
Esto significa que una constante doble se puede convertir implícitamente (es decir, en silencio) en una constante flotante, incluso si al hacerlo se pierde precisión (es decir, datos). Se permitió que se mantuviera por razones de compatibilidad y usabilidad de C, pero vale la pena tenerlo en cuenta cuando se trabaja en punto flotante.
Un compilador de calidad le advertirá si intenta hacer algo que tiene un comportamiento indefinido, es decir, poner una cantidad doble en un valor flotante que sea menor que el valor mínimo, o mayor que el máximo, que un valor flotante es capaz de representar. Un compilador realmente bueno proporcionará una advertencia opcional si intenta hacer algo que puede estar definido pero podría perder información, es decir, poner una cantidad doble en un flotador que esté entre los valores mínimo y máximo representables por un flotador, pero que no puede representarse exactamente como un flotador.
Por lo tanto, hay advertencias para el caso general que debe tener en cuenta.
Desde una perspectiva práctica, en este caso, los resultados probablemente serán los mismos, aunque técnicamente hay una conversión, podemos ver esto probando el siguiente código en godbolt :
#include <iostream>
float func1()
{
return 3.0; // a double literal
}
float func2()
{
return 3.0f ; // a float literal
}
int main()
{
std::cout << func1() << ":" << func2() << std::endl ;
return 0;
}
y vemos que los resultados para func1
y func2
son idénticos, usando tanto clang
y gcc
:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
Como señala Pascal en este comentario , no siempre podrás contar con esto. El uso de 0.1
y 0.1f
respectivamente hace que el ensamblado generado sea diferente, ya que la conversión ahora debe realizarse explícitamente. El siguiente código:
float func1(float x )
{
return x*0.1; // a double literal
}
float func2(float x)
{
return x*0.1f ; // a float literal
}
da como resultado el siguiente montaje:
func1(float):
cvtss2sd %xmm0, %xmm0 # x, D.31147
mulsd .LC0(%rip), %xmm0 #, D.31147
cvtsd2ss %xmm0, %xmm0 # D.31147, D.31148
ret
func2(float):
mulss .LC2(%rip), %xmm0 #, D.31155
ret
Independientemente de si puede determinar si la conversión tendrá un impacto en el rendimiento o no, utilizar el tipo correcto documenta mejor su intención. El uso de conversiones explícitas, por ejemplo, static_cast
también ayuda a aclarar que la conversión fue intencionada en lugar de accidental, lo que puede significar un error o un error potencial.
Nota
Como señala supercat, la multiplicación por, por ejemplo, 0.1
y 0.1f
no es equivalente. Solo voy a citar el comentario porque fue excelente y un resumen probablemente no le haría justicia:
Por ejemplo, si f era igual a 100000224 (que es exactamente representable como un flotador), multiplicarlo por una décima debería producir un resultado que se redondea hacia abajo a 10000022, pero multiplicar por 0,1 f en su lugar producirá un resultado que erróneamente se redondea a 10000023 Si la intención es dividir por diez, la multiplicación por doble constante 0.1 probablemente será más rápida que la división por 10f y más precisa que la multiplicación por 0.1f.
Mi punto original era demostrar un ejemplo falso dado en otra pregunta, pero esto demuestra finamente que pueden existir problemas sutiles en los ejemplos de juguetes.
f = f * 0.1;
y f = f * 0.1f;
hacen cosas diferentes . Por ejemplo, si f
era igual a 100000224 (que es exactamente representable como a float
), multiplicarlo por una décima debería arrojar un resultado que se redondea a la baja a 10000022, pero multiplicar por 0.1f en su lugar producirá un resultado que erróneamente se redondea a 10000023. Si la intención es dividir por diez, la multiplicación por la double
constante 0.1 probablemente será más rápida que la división por 10f
y más precisa que la multiplicación por 0.1f
.
No es un error en el sentido de que el compilador lo rechazará, pero es un error en el sentido de que puede que no sea lo que desea.
Como dice correctamente su libro, 3.0
es un valor de tipo double
. Hay una conversión implícita de double
a float
, por lo que float a = 3.0;
es una definición válida de una variable.
Sin embargo, al menos conceptualmente, esto realiza una conversión innecesaria. Dependiendo del compilador, la conversión se puede realizar en tiempo de compilación o se puede guardar para tiempo de ejecución. Una razón válida para guardarlo para el tiempo de ejecución es que las conversiones de punto flotante son difíciles y pueden tener efectos secundarios inesperados si el valor no se puede representar exactamente, y no siempre es fácil verificar si el valor se puede representar exactamente.
3.0f
evita ese problema: aunque técnicamente, el compilador todavía puede calcular la constante en tiempo de ejecución (siempre lo es), aquí, no hay absolutamente ninguna razón por la que cualquier compilador pueda hacer eso.
Aunque no es un error, en sí mismo, es un poco descuidado. Sabes que quieres un flotador, así que inicialízalo con un flotador.
Puede aparecer otro programador y no estar seguro de qué parte de la declaración es correcta, el tipo o el inicializador. ¿Por qué no hacer que ambos sean correctos?
respuesta flotante = 42.0f;
Cuando define una variable, se inicializa con el inicializador proporcionado. Esto puede requerir convertir el valor del inicializador al tipo de variable que se está inicializando. Eso es lo que sucede cuando dice float a = 3.0;
: el valor del inicializador se convierte en float
y el resultado de la conversión se convierte en el valor inicial dea
.
En general, eso está bien, pero no está de más escribir 3.0f
para demostrar que eres consciente de lo que estás haciendo, y especialmente si quieres escribir auto a = 3.0f
.
Si prueba lo siguiente:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
obtendrá una salida como:
4:8
que muestra, el tamaño de 3.2f se toma como 4 bytes en una máquina de 32 bits, mientras que 3.2 se interpreta como un valor doble que toma 8 bytes en una máquina de 32 bits. Esto debería proporcionar la respuesta que está buscando.
double
y float
son diferentes, no responde si puede inicializar a float
desde un doble literal
El compilador deduce el tipo que mejor se ajusta a los literales, o al menos lo que cree que se ajusta mejor. Eso es más bien perder eficiencia sobre precisión, es decir, utilizar un doble en lugar de un flotador. En caso de duda, utilice brace-inicializadores para hacerlo explícito:
auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int
La historia se vuelve más interesante si inicializa desde otra variable donde se aplican las reglas de conversión de tipo: si bien es legal construir una forma doble de un literal, no se puede construir a partir de un int sin un posible estrechamiento:
auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'
3.0
en un flotante por usted. El resultado final es indistinguible defloat a = 3.0f
.