Si j'ai la déclaration suivante:
float a = 3.0 ;
est-ce une erreur? J'ai lu dans un livre qui 3.0
est une double
valeur et que je dois spécifier comme float a = 3.0f
. Est-ce vrai?
Si j'ai la déclaration suivante:
float a = 3.0 ;
est-ce une erreur? J'ai lu dans un livre qui 3.0
est une double
valeur et que je dois spécifier comme float a = 3.0f
. Est-ce vrai?
;
après.
Réponses:
Ce n'est pas une erreur à déclarer float a = 3.0
: si vous le faites, le compilateur convertira le double littéral 3.0 en float pour vous.
, Toutefois vous devez utiliser la notation de littéraux float dans des scénarios spécifiques.
Pour des raisons de performances:
Plus précisément, considérez:
float foo(float x) { return x * 0.42; }
Ici, le compilateur émettra une conversion (que vous paierez à l'exécution) pour chaque valeur renvoyée. Pour éviter cela, vous devez déclarer:
float foo(float x) { return x * 0.42f; } // OK, no conversion required
Pour éviter les bugs lors de la comparaison des résultats:
par exemple, la comparaison suivante échoue:
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
Nous pouvons le corriger avec la notation littérale float:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
(Remarque: bien sûr, ce n'est pas ainsi que vous devez comparer des nombres flottants ou doubles pour l'égalité en général )
Pour appeler la fonction surchargée correcte (pour la même raison):
Exemple:
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;
}
Comme indiqué par Cyber , dans un contexte de déduction de type, il est nécessaire d'aider le compilateur à déduire un float
:
En cas de auto
:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Et de même, en cas de déduction de type modèle:
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
trouve un entier, qui est automatiquement promu vers float
(et cela se produira au moment de la compilation dans n'importe quel compilateur décent), donc il n'y a pas de pénalité de performance. Vous vouliez probablement dire quelque chose comme 42.0
.
4.2
en 4.2f
peut avoir pour effet secondaire de définir l' FE_INEXACT
indicateur, en fonction du compilateur et du système, et certains (certes peu) programmes se soucient des opérations en virgule flottante exactes et de celles qui ne le sont pas, et testent cet indicateur . Cela signifie que la simple transformation évidente au moment de la compilation modifie le comportement du programme.
float foo(float x) { return x*42.0; }
peut être compilé en une multiplication simple précision, et a été compilé ainsi par Clang la dernière fois que j'ai essayé. Cependant, float foo(float x) { return x*0.1; }
ne peut pas être compilé en une seule multiplication simple précision. C'était peut-être un peu trop optimiste avant ce patch, mais après le patch, il ne devrait combiner conversion-double_precision_op-conversion en single_precision_op que lorsque le résultat est toujours le même. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
someFloat
, l'expression someFloat * 0.1
donnera des résultats plus précis que someFloat * 0.1f
, tout en étant dans de nombreux cas moins chère qu'une division en virgule flottante. Par exemple, (float) (167772208.0f * 0.1) arrondira correctement à 16777220 plutôt qu'à 16777222. Certains compilateurs peuvent substituer une double
multiplication à une division en virgule flottante, mais pour ceux qui ne le font pas (c'est sûr pour beaucoup mais pas pour toutes les valeurs ) la multiplication peut être une optimisation utile, mais seulement si elle est effectuée avec une double
réciproque.
Le compilateur transformera n'importe lequel des littéraux suivants en flottants, car vous avez déclaré la variable en tant que flottant.
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
Il serait important que vous utilisiez auto
(ou d'autres méthodes de déduction de type), par exemple:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
auto
n'est donc pas le seul cas.
Les littéraux à virgule flottante sans suffixe sont de type double , ceci est traité dans le projet de section standard C ++ 2.14.4
Littéraux flottants :
[...] Le type d'un littéral flottant est double sauf s'il est explicitement spécifié par un suffixe. [...]
alors est-ce une erreur d'assigner 3.0
un double littéral à un flottant ?
float a = 3.0
Non, ce n'est pas le cas, il sera converti, ce qui est couvert dans la section 4.8
Conversions en virgule flottante :
Une prvalue de type à virgule flottante peut être convertie en une prvalue d'un autre type à virgule flottante. Si la valeur source peut être exactement représentée dans le type de destination, le résultat de la conversion est cette représentation exacte. Si la valeur source se situe entre deux valeurs de destination adjacentes, le résultat de la conversion est un choix défini par l'implémentation de l'une de ces valeurs. Sinon, le comportement n'est pas défini.
Nous pouvons lire plus de détails sur les implications de cela dans GotW # 67: double ou rien qui dit:
Cela signifie qu'une constante double peut être implicitement (c'est-à-dire silencieusement) convertie en constante flottante, même si cela perd de la précision (c'est-à-dire des données). Cela a été autorisé pour des raisons de compatibilité et d'utilisabilité C, mais cela vaut la peine de garder à l'esprit lorsque vous travaillez en virgule flottante.
Un compilateur de qualité vous avertira si vous essayez de faire quelque chose dont le comportement n'est pas défini, à savoir mettre une quantité double dans un flottant qui est inférieure à la valeur minimale ou supérieure à la valeur maximale qu'un flottant est capable de représenter. Un très bon compilateur fournira un avertissement facultatif si vous essayez de faire quelque chose qui peut être défini mais qui pourrait perdre des informations, à savoir mettre une quantité double dans un flottant qui se situe entre les valeurs minimale et maximale représentables par un flottant, mais qui ne peut pas être représenté exactement comme un flotteur.
Il y a donc des mises en garde pour le cas général dont vous devez être conscient.
D'un point de vue pratique, dans ce cas, les résultats seront très probablement les mêmes même s'il y a techniquement une conversion, nous pouvons le voir en essayant le code suivant sur 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;
}
et nous voyons que les résultats pour func1
et func2
sont identiques, en utilisant à la fois clang
et gcc
:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
Comme le souligne Pascal dans ce commentaire, vous ne pourrez pas toujours compter dessus. L'utilisation de 0.1
et 0.1f
entraîne respectivement une différence entre l'assembly généré car la conversion doit maintenant être effectuée explicitement. Le code suivant:
float func1(float x )
{
return x*0.1; // a double literal
}
float func2(float x)
{
return x*0.1f ; // a float literal
}
aboutit à l'assemblage suivant:
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
Que vous puissiez déterminer si la conversion aura un impact sur les performances ou non, l'utilisation du type correct documente mieux votre intention. L'utilisation de conversions explicites, par exemple, static_cast
aide également à clarifier que la conversion était destinée par opposition à accidentelle, ce qui peut signifier un bogue ou un bogue potentiel.
Remarque
Comme le souligne supercat, la multiplication par par exemple 0.1
et 0.1f
n'est pas équivalente. Je vais simplement citer le commentaire car il était excellent et un résumé ne lui rendrait probablement pas justice:
Par exemple, si f était égal à 100000224 (ce qui est exactement représentable sous forme de flottant), le multiplier par un dixième devrait donner un résultat arrondi à 10000022, mais multiplier par 0,1f donnera à la place un résultat qui arrondit par erreur à 10000023 Si l'intention est de diviser par dix, la multiplication par la constante double 0,1 sera probablement plus rapide que la division par 10f, et plus précise que la multiplication par 0,1f.
Mon point initial était de démontrer un faux exemple donné dans une autre question, mais cela démontre finement que des problèmes subtils peuvent exister dans les exemples de jouets.
f = f * 0.1;
et f = f * 0.1f;
faire des choses différentes . Par exemple, si f
était égal à 100000224 (qui est exactement représentable comme a float
), le multiplier par un dixième devrait donner un résultat arrondi à 10000022, mais multiplier par 0,1f donnera à la place un résultat qui arrondit par erreur à 10000023. Si l'intention est de diviser par dix, la multiplication par la double
constante 0,1 sera probablement plus rapide que la division par 10f
, et plus précise que la multiplication par 0.1f
.
Ce n'est pas une erreur dans le sens où le compilateur le rejettera, mais c'est une erreur dans le sens où ce n'est peut-être pas ce que vous voulez.
Comme votre livre l'indique correctement, 3.0
est une valeur de type double
. Il y a une conversion implicite de double
à float
, de même float a = 3.0;
qu'une définition valide d'une variable.
Cependant, au moins conceptuellement, cela effectue une conversion inutile. Selon le compilateur, la conversion peut être effectuée au moment de la compilation ou elle peut être enregistrée pour l'exécution. Une raison valable pour l'enregistrer pour l'exécution est que les conversions en virgule flottante sont difficiles et peuvent avoir des effets secondaires inattendus si la valeur ne peut pas être représentée exactement, et il n'est pas toujours facile de vérifier si la valeur peut être représentée exactement.
3.0f
évite ce problème: bien que techniquement, le compilateur soit toujours autorisé à calculer la constante au moment de l'exécution (c'est toujours le cas), ici, il n'y a absolument aucune raison pour qu'un compilateur puisse le faire.
Bien que ce ne soit pas une erreur en soi, c'est un peu bâclé. Vous savez que vous voulez un float, alors initialisez-le avec un float.
Un autre programmeur peut venir et ne pas savoir quelle partie de la déclaration est correcte, le type ou l'initialiseur. Pourquoi ne pas avoir tous les deux raison?
Réponse flottante = 42,0f;
Lorsque vous définissez une variable, elle est initialisée avec l'initialiseur fourni. Cela peut nécessiter de convertir la valeur de l'initialiseur en type de variable en cours d'initialisation. C'est ce qui se passe lorsque vous dites float a = 3.0;
: la valeur de l'initialiseur est convertie en float
, et le résultat de la conversion devient la valeur initiale de a
.
C'est généralement bien, mais cela ne fait pas de mal d'écrire 3.0f
pour montrer que vous êtes conscient de ce que vous faites, et surtout si vous voulez écrire auto a = 3.0f
.
Si vous essayez ce qui suit:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
vous obtiendrez la sortie comme:
4:8
cela montre, la taille de 3.2f est prise comme 4 octets sur une machine 32 bits alors que 3.2 est interprétée comme une valeur double prenant 8 octets sur une machine 32 bits. Cela devrait fournir la réponse que vous recherchez.
double
et float
sont différents, cela ne répond pas si vous pouvez initialiser un à float
partir d'un double littéral
Le compilateur déduit le type le mieux adapté à partir de littéraux, ou au moins ce qu'il pense être le mieux adapté. C'est plutôt perdre de l'efficacité par rapport à la précision, c'est-à-dire utiliser un double au lieu d'un flotteur. En cas de doute, utilisez des accolades pour le rendre explicite:
auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int
L'histoire devient plus intéressante si vous initialisez à partir d'une autre variable où les règles de conversion de type s'appliquent: bien qu'il soit légal de construire un double sous forme de littéral, il ne peut pas être construit à partir d'un int sans rétrécissement possible:
auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'
3.0
en float pour vous. Le résultat final est indiscernable defloat a = 3.0f
.