Est-ce que 'float a = 3.0;' une déclaration correcte?

86

Si j'ai la déclaration suivante:

float a = 3.0 ;

est-ce une erreur? J'ai lu dans un livre qui 3.0est une doublevaleur et que je dois spécifier comme float a = 3.0f. Est-ce vrai?

TESLA____
la source
2
Le compilateur convertira le double littéral 3.0en float pour vous. Le résultat final est indiscernable de float a = 3.0f.
David Heffernan
6
@EdHeal: C'est vrai, mais ce n'est pas particulièrement pertinent pour cette question, qui concerne les règles C ++.
Keith Thompson
20
Eh bien, au moins, vous avez besoin d'un ;après.
Hot Licks
3
10 votes négatifs et pas grand chose dans les commentaires pour les expliquer, très décourageant. C'est la première question des PO et si les gens estiment que cela vaut 10 votes négatifs, il devrait y avoir quelques explications. C'est une question valable avec des implications non évidentes et beaucoup de choses intéressantes à apprendre des réponses et des commentaires.
Shafik Yaghmour
3
@HotLicks il ne s'agit pas de se sentir mal ou bien, bien sûr que cela peut sembler injuste mais c'est la vie, ce sont des points de licorne après tout. Les votes positifs ne sont certainement pas pour annuler les votes positifs que vous n'aimez pas, tout comme les votes positifs ne sont pas pour annuler les votes négatifs que vous n'aimez pas. Si les gens pensent que la question peut être améliorée, un premier demandeur devrait sûrement obtenir des commentaires. Je ne vois aucune raison de voter contre mais j'aimerais savoir pourquoi les autres le font bien qu'ils soient libres de ne pas le dire.
Shafik Yaghmour

Réponses:

159

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.

  1. 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
    
  2. 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 )

  3. 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;
    }
    
  4. 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;
    }
    

Démo en direct

quantdev
la source
2
Au point 1 se 42trouve 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.
Matteo Italia
@MatteoItalia, oui je voulais dire 42.0 ofc (édité, merci)
quantdev
2
@ChristianHackl La conversion 4.2en 4.2fpeut avoir pour effet secondaire de définir l' FE_INEXACTindicateur, 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.
6
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=
Pascal Cuoq
1
Si l'on souhaite calculer une valeur égale à un dixième de someFloat, l'expression someFloat * 0.1donnera 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 doublemultiplication à 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 doubleréciproque.
supercat du
22

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
Cory Kramer
la source
5
Les types sont également déduits lors de l'utilisation de modèles, ce auton'est donc pas le seul cas.
Shafik Yaghmour
14

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.0un 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 func1et func2sont identiques, en utilisant à la fois clanget 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.1et 0.1fentraî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_castaide é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.1et 0.1fn'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.

Shafik Yaghmour
la source
1
Il peut être intéressant de noter que les expressions 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 doubleconstante 0,1 sera probablement plus rapide que la division par 10f, et plus précise que la multiplication par 0.1f.
supercat du
@supercat merci pour le bel exemple, je vous ai cité directement, n'hésitez pas à éditer comme bon vous semble.
Shafik Yaghmour
4

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.0est 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.


la source
En effet, dans le cas d'un compilateur croisé, il serait tout à fait incorrect que la conversion soit effectuée au moment de la compilation, car elle se produirait sur la mauvaise plateforme.
Marquis de Lorne
2

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;

Ingénieur
la source
0

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.0fpour montrer que vous êtes conscient de ce que vous faites, et surtout si vous voulez écrire auto a = 3.0f.

Kerrek SB
la source
0

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.

Dr Debasish Jana
la source
Cela montre que doubleet floatsont différents, cela ne répond pas si vous pouvez initialiser un à floatpartir d'un double littéral
Jonathan Wakely
bien sûr, vous pouvez initialiser un flottant à partir d'une valeur double soumise à la troncature des données, le cas échéant
Dr. Debasish Jana
4
Oui, je sais, mais c'était la question du PO, donc votre réponse n'a pas réussi à y répondre, même si vous prétendez fournir la réponse!
Jonathan Wakely
0

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' 
truschival
la source