É 'float a = 3.0;' uma afirmação correta?


86

Se eu tiver a seguinte declaração:

float a = 3.0 ;

isso é um erro? Li em um livro que 3.0é um doublevalor e que devo especificá-lo como float a = 3.0f. É assim?


2
O compilador converterá o literal duplo 3.0em um float para você. O resultado final é indistinguível de float a = 3.0f.
David Heffernan

6
@EdHeal: É, mas não é particularmente relevante para esta questão, que é sobre regras C ++.
Keith Thompson

20
Bem, pelo menos você precisa de um ;depois.
Hot Licks

3
10 votos negativos e não muito nos comentários para explicá-los, muito desanimador. Esta é a primeira pergunta do OP e se as pessoas acharem que vale 10 votos negativos, deve haver algumas explicações. Esta é uma pergunta válida com implicações não óbvias e muitas coisas interessantes para aprender com as respostas e comentários.
Shafik Yaghmour

3
@HotLicks não se trata de se sentir mal ou bem, com certeza pode parecer injusto, mas isso é a vida, eles são pontos de unicórnio afinal. Os votos favoráveis ​​certamente não são para cancelar os votos positivos de que você não gosta, assim como os votos positivos não devem cancelar os votos negativos de que você não gosta. Se as pessoas sentirem que a pergunta pode ser melhorada, certamente quem pergunta pela primeira vez deve receber algum feedback. Não vejo nenhuma razão para votar negativamente, mas gostaria de saber por que os outros o fazem, embora sejam livres para não dizer isso.
Shafik Yaghmour

Respostas:


159

Não é um erro declarar float a = 3.0: se o fizer, o compilador converterá o duplo literal 3.0 em um float para você.


No entanto, você deve usar a notação de literais flutuantes em cenários específicos.

  1. Por motivos de desempenho:

    Especificamente, considere:

    float foo(float x) { return x * 0.42; }

    Aqui, o compilador emitirá uma conversão (que você pagará no tempo de execução) para cada valor retornado. Para evitá-lo, você deve declarar:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
  2. Para evitar bugs ao comparar os resultados:

    por exemplo, a seguinte comparação falha:

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!
    

    Podemos corrigir isso com a notação literal float:

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!
    

    (Observação: é claro, não é assim que você deve comparar números flutuantes ou duplos para igualdade em geral )

  3. Para chamar a função sobrecarregada correta (pelo mesmo motivo):

    Exemplo:

    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. Conforme observado pela Cyber , em um contexto de dedução de tipo, é necessário ajudar o compilador a deduzir float:

    No caso de auto:

    auto d = 3;      // int
    auto e = 3.0;    // double
    auto f = 3.0f;   // float
    

    E da mesma forma, no caso de dedução do tipo de modelo:

    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;
    }

Demonstração ao vivo


2
No ponto 1 42está um inteiro, que é automaticamente promovido para float(e isso acontecerá no momento da compilação em qualquer compilador decente), portanto, não há penalidade de desempenho. Provavelmente você quis dizer algo assim 42.0.
Matteo Italia

@MatteoItalia, sim, eu quis dizer 42.0 ofc (editado, obrigado)
quantdev

2
@ChristianHackl Convertendo 4.2para 4.2fpode ter o efeito colateral de definir a FE_INEXACTbandeira, dependendo do compilador e do sistema, e alguns (reconhecidamente poucos) programas se preocupam com que as operações de ponto flutuante são exatas, e que não são, e teste para que a bandeira . Isso significa que a transformação simples e óbvia em tempo de compilação altera o comportamento do programa.

6
float foo(float x) { return x*42.0; }pode ser compilado para uma multiplicação de precisão simples e foi compilado pelo Clang da última vez que tentei. No entanto, float foo(float x) { return x*0.1; }não pode ser compilado para uma única multiplicação de precisão única. Pode ter sido um pouco otimista demais antes deste patch, mas depois do patch ele deve apenas combinar conversion-double_precision_op-conversion a single_precision_op quando o resultado é sempre o mesmo. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq

1
Se alguém deseja calcular um valor que é um décimo de someFloat, a expressão someFloat * 0.1terá resultados mais precisos do que someFloat * 0.1f, embora em muitos casos seja mais barata do que uma divisão de ponto flutuante. Por exemplo, (float) (167772208.0f * 0.1) será arredondado corretamente para 16777220 em vez de 16777222. Alguns compiladores podem substituir uma doublemultiplicação por uma divisão de ponto flutuante, mas para aqueles que não o fazem (é seguro para muitos, mas não todos os valores ) a multiplicação pode ser uma otimização útil, mas apenas se realizada com um doublerecíproco.
supercat

22

O compilador transformará qualquer um dos seguintes literais em flutuantes, porque você declarou a variável como flutuante.

float a = 3;     // converted to float
float b = 3.0;   // converted to float
float c = 3.0f;  // float

Importaria se você usasse auto(ou outro tipo de método de dedução), por exemplo:

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float

5
Os tipos também são deduzidos ao usar modelos, portanto, autonão é o único caso.
Shafik Yaghmour

14

Literais de ponto flutuante sem um sufixo são do tipo duplo , isso é abordado no rascunho da seção padrão C ++ 2.14.4 Literais flutuantes :

[...] O tipo de um literal flutuante é duplo, a menos que seja explicitamente especificado por um sufixo. [...]

então, é um erro atribuir 3.0um literal duplo a um float ?:

float a = 3.0

Não, ele será convertido, o que é abordado na seção 4.8 Conversões de ponto flutuante :

Um prvalue do tipo de ponto flutuante pode ser convertido em um prvalue de outro tipo de ponto flutuante. Se o valor de origem pode ser representado exatamente no tipo de destino, o resultado da conversão é essa representação exata. Se o valor de origem estiver entre dois valores de destino adjacentes, o resultado da conversão é uma escolha definida pela implementação de qualquer um desses valores. Caso contrário, o comportamento é indefinido.

Podemos ler mais detalhes sobre as implicações disso em GotW # 67: double or nothing que diz:

Isso significa que uma constante dupla pode ser convertida implicitamente (ou seja, silenciosamente) em uma constante flutuante, mesmo que isso perca a precisão (ou seja, dados). Isso foi permitido por motivos de compatibilidade e usabilidade do C, mas vale a pena ter em mente quando você faz o trabalho de ponto flutuante.

Um compilador de qualidade irá avisá-lo se você tentar fazer algo que tenha um comportamento indefinido, ou seja, colocar uma quantidade dupla em um valor flutuante que seja menor que o mínimo ou maior que o valor máximo que um valor flutuante é capaz de representar. Um compilador realmente bom fornecerá um aviso opcional se você tentar fazer algo que pode ser definido, mas pode perder informações, ou seja, colocar uma quantidade dupla em um float que está entre os valores mínimo e máximo representáveis ​​por um float, mas que não pode ser representado exatamente como um flutuador.

Portanto, há ressalvas para o caso geral das quais você deve estar ciente.

De uma perspectiva prática, neste caso, os resultados provavelmente serão os mesmos, embora tecnicamente haja uma conversão, podemos ver isso experimentando o seguinte código em 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;
}

e vemos que os resultados para func1e func2são idênticos, usando clange gcc:

func1():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret
func2():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret

Como Pascal aponta neste comentário, você nem sempre poderá contar com isso. Usar 0.1e, 0.1frespectivamente, faz com que o assembly gerado seja diferente, pois a conversão agora deve ser feita explicitamente. O seguinte código:

float func1(float x )
{
  return x*0.1; // a double literal
}

float func2(float x)
{
  return x*0.1f ; // a float literal
}

resulta na seguinte montagem:

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

Independentemente de saber se você pode determinar se a conversão terá um impacto no desempenho ou não, o uso do tipo correto documenta melhor sua intenção. Usar uma conversão explícita, por exemplo, static_casttambém ajuda a esclarecer se a conversão foi intencional e não acidental, o que pode significar um bug ou um bug em potencial.

Nota

Como supercat aponta, multiplicação por eg 0.1e 0.1fnão é equivalente. Vou apenas citar o comentário porque foi excelente e um resumo provavelmente não faria justiça:

Por exemplo, se f fosse igual a 100000224 (que é exatamente representável como um float), multiplicá-lo por um décimo deve render um resultado arredondado para 10000022, mas multiplicar por 0,1f produzirá, em vez disso, um resultado que arredonda erroneamente para 10000023 Se a intenção é dividir por dez, a multiplicação pela constante dupla 0,1 será provavelmente mais rápida do que a divisão por 10f e mais precisa do que a multiplicação por 0,1f.

Meu objetivo original era demonstrar um exemplo falso dado em outra pergunta, mas isso demonstra que problemas sutis podem existir em exemplos de brinquedos.


1
Pode ser interessante notar que as expressões f = f * 0.1;e f = f * 0.1f; fazem coisas diferentes . Por exemplo, se ffosse igual a 100000224 (que é exatamente representável como a float), multiplicá-lo por um décimo deve produzir um resultado arredondado para 10000022, mas multiplicar por 0,1f produzirá, em vez disso, um resultado que arredondará erroneamente para 10000023. Se a intenção é dividir por dez, a multiplicação por double0,1 constante provavelmente será mais rápida do que a divisão por 10fe mais precisa do que a multiplicação por 0.1f.
supercat

@supercat obrigado pelo bom exemplo, eu citei você diretamente, por favor, sinta-se à vontade para editar como achar necessário.
Shafik Yaghmour

4

Não é um erro no sentido de que o compilador irá rejeitá-lo, mas é um erro no sentido de que pode não ser o que você deseja.

Como seu livro afirma corretamente, 3.0é um valor do tipo double. Há uma conversão implícita de doublepara float, então float a = 3.0;é uma definição válida de uma variável.

No entanto, pelo menos conceitualmente, isso executa uma conversão desnecessária. Dependendo do compilador, a conversão pode ser realizada em tempo de compilação ou pode ser salva para tempo de execução. Um motivo válido para salvá-lo para o tempo de execução é que as conversões de ponto flutuante são difíceis e podem ter efeitos colaterais inesperados se o valor não puder ser representado exatamente e nem sempre é fácil verificar se o valor pode ser representado exatamente.

3.0f evita esse problema: embora tecnicamente, o compilador ainda pode calcular a constante em tempo de execução (sempre é), aqui, não há absolutamente nenhuma razão para que qualquer compilador possa fazer isso.


De fato, no caso de um compilador cruzado, seria bastante incorreto que a conversão fosse realizada em tempo de compilação, porque estaria ocorrendo na plataforma errada.
Marquês de Lorne

2

Embora não seja um erro, por si só, é um pouco desleixado. Você sabe que quer um float, então inicialize-o com um float.
Outro programador pode aparecer e não ter certeza de qual parte da declaração está correta, o tipo ou o inicializador. Por que não fazer com que ambos estejam corretos?
float Answer = 42.0f;


0

Quando você define uma variável, ela é inicializada com o inicializador fornecido. Isso pode exigir a conversão do valor do inicializador para o tipo da variável que está sendo inicializada. Isso é o que acontece quando você diz float a = 3.0;: o valor do inicializador é convertido para floate o resultado da conversão se torna o valor inicial de a.

Em geral, não há problema, mas não custa nada escrever 3.0fpara mostrar que você está ciente do que está fazendo e, principalmente, se deseja escrever auto a = 3.0f.


0

Se você tentar o seguinte:

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

você obterá a saída como:

4:8

que mostra, o tamanho de 3.2f é considerado como 4 bytes na máquina de 32 bits, enquanto 3.2 é interpretado como valor duplo levando 8 bytes na máquina de 32 bits. Isso deve fornecer a resposta que você está procurando.


Isso mostra que doublee floatsão diferentes, não responde se você pode inicializar a a floatpartir de um literal duplo
Jonathan Wakely

claro que você pode inicializar um float de um valor duplo sujeito a truncamento de dados, se aplicável
Dr. Debasish Jana

4
Sim, eu sei, mas essa era a pergunta do OP, então sua resposta não conseguiu realmente respondê-la, apesar de alegar ter fornecido a resposta!
Jonathan Wakely

0

O compilador deduz o tipo mais adequado a partir dos literais, ou pelo menos o que ele acha que é o mais adequado. Isso é perder eficiência em vez de precisão, ou seja, usar um double em vez de float. Em caso de dúvida, use os inicializadores de chaves para torná-lo explícito:

auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int

A história fica mais interessante se você inicializar a partir de outra variável onde as regras de conversão de tipo se aplicam: Embora seja legal construir uma forma dupla de literal, não pode ser construído a partir de um int sem possível estreitamento:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.