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.
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
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:
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 floatfloat b =3.0;// converted to floatfloat c =3.0f;// float
Importaria se você usasse auto(ou outro tipo de método de dedução), por exemplo:
auto d =3;// intauto e =3.0;// doubleauto f =3.0f;// float
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.4Literais 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.8Conversõ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.
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(){return3.0;// a double literal}float func2(){return3.0f;// a float literal}int main(){
std::cout << func1()<<":"<< func2()<< std::endl ;return0;}
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}
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.
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;
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.
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 doubleauto f =float{3};// make a floatauto 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'
3.0
em um float para você. O resultado final é indistinguível defloat a = 3.0f
.;
depois.Respostas:
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.
Por motivos de desempenho:
Especificamente, considere:
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:
Para evitar bugs ao comparar os resultados:
por exemplo, a seguinte comparação falha:
Podemos corrigir isso com a notação literal float:
(Observação: é claro, não é assim que você deve comparar números flutuantes ou duplos para igualdade em geral )
Para chamar a função sobrecarregada correta (pelo mesmo motivo):
Exemplo:
Conforme observado pela Cyber , em um contexto de dedução de tipo, é necessário ajudar o compilador a deduzir
float
:No caso de
auto
:E da mesma forma, no caso de dedução do tipo de modelo:
Demonstração ao vivo
fonte
42
está um inteiro, que é automaticamente promovido parafloat
(e isso acontecerá no momento da compilação em qualquer compilador decente), portanto, não há penalidade de desempenho. Provavelmente você quis dizer algo assim42.0
.4.2
para4.2f
pode ter o efeito colateral de definir aFE_INEXACT
bandeira, 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.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=someFloat
, a expressãosomeFloat * 0.1
terá resultados mais precisos do quesomeFloat * 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 umadouble
multiplicaçã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 umdouble
recíproco.O compilador transformará qualquer um dos seguintes literais em flutuantes, porque você declarou a variável como flutuante.
Importaria se você usasse
auto
(ou outro tipo de método de dedução), por exemplo:fonte
auto
não é o único caso.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 :então, é um erro atribuir
3.0
um literal duplo a um float ?:Não, ele será convertido, o que é abordado na seção
4.8
Conversões de ponto flutuante :Podemos ler mais detalhes sobre as implicações disso em GotW # 67: double or nothing que diz:
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 :
e vemos que os resultados para
func1
efunc2
são idênticos, usandoclang
egcc
:Como Pascal aponta neste comentário, você nem sempre poderá contar com isso. Usar
0.1
e,0.1f
respectivamente, faz com que o assembly gerado seja diferente, pois a conversão agora deve ser feita explicitamente. O seguinte código:resulta na seguinte montagem:
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_cast
també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.1
e0.1f
não é equivalente. Vou apenas citar o comentário porque foi excelente e um resumo provavelmente não faria justiça:Meu objetivo original era demonstrar um exemplo falso dado em outra pergunta, mas isso demonstra que problemas sutis podem existir em exemplos de brinquedos.
fonte
f = f * 0.1;
ef = f * 0.1f;
fazem coisas diferentes . Por exemplo, sef
fosse igual a 100000224 (que é exatamente representável como afloat
), 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 pordouble
0,1 constante provavelmente será mais rápida do que a divisão por10f
e mais precisa do que a multiplicação por0.1f
.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 tipodouble
. Há uma conversão implícita dedouble
parafloat
, entãofloat 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.fonte
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;
fonte
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 parafloat
e o resultado da conversão se torna o valor inicial dea
.Em geral, não há problema, mas não custa nada escrever
3.0f
para mostrar que você está ciente do que está fazendo e, principalmente, se deseja escreverauto a = 3.0f
.fonte
Se você tentar o seguinte:
você obterá a saída como:
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.
fonte
double
efloat
são diferentes, não responde se você pode inicializar a afloat
partir de um literal duploO 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:
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:
fonte