'Float a = 3.0;' doğru bir ifade?


86

Aşağıdaki beyana sahipsem:

float a = 3.0 ;

bu bir hata mı? Bir kitapta okunan 3.0bir olan doubledeğer ve sahip olduğum olarak belirtmek float a = 3.0f. Öyle mi?


2
Derleyici, double literali 3.0sizin için bir float'a dönüştürecektir. Sonuç, ile ayırt edilemez float a = 3.0f.
David Heffernan

6
@EdHeal: Öyle, ancak C ++ kuralları ile ilgili olan bu soruyla özellikle alakalı değil.
Keith Thompson

20
En azından bir ;sonraya ihtiyacın var .
Hot Licks

3
10 olumsuz oy ve yorumlarda onları açıklamak için çok fazla değil, çok cesaret kırıcı. Bu, OP'lerin ilk sorusudur ve eğer insanlar bunun 10 eksi oy değerinde olduğunu düşünüyorsa, bazı açıklamalar yapılmalıdır. Bu, açık olmayan sonuçları olan ve cevaplardan ve yorumlardan öğrenilecek pek çok ilginç şeyi olan geçerli bir sorudur.
Shafik Yaghmour

3
@HotLicks kötü ya da iyi hissetmekle ilgili değil, elbette haksız görünebilir ama hayat bu, sonuçta tek boynuzlu at noktaları. Dowvotes, kesinlikle beğenmediğiniz olumlu oyları iptal etmeyecek, tıpkı olumlu oyların beğenmediğiniz olumsuz oyları iptal etmemesi gibi. İnsanlar sorunun geliştirilebileceğini düşünüyorsa, kuşkusuz ilk kez soran kişi biraz geri bildirim almalıdır. Olumsuz oy vermek için herhangi bir neden görmüyorum ama başkalarının söylememekte özgür olmalarına rağmen neden yaptıklarını bilmek istiyorum.
Shafik Yaghmour

Yanıtlar:


159

Bildirmek bir hata değildir float a = 3.0: eğer yaparsanız, derleyici çift değişmez 3.0'ı sizin için bir float'a dönüştürecektir.


Ancak, gereken belirli senaryolarda şamandıra değişmezleri notasyonu kullanın.

  1. Performans nedenleriyle:

    Özellikle şunları göz önünde bulundurun:

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

    Burada derleyici, döndürülen her değer için (çalışma zamanında ödeyeceğiniz) bir dönüşüm yayınlayacaktır. Bundan kaçınmak için şunları beyan etmelisiniz:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. Sonuçları karşılaştırırken hataları önlemek için:

    örneğin aşağıdaki karşılaştırma başarısız olur:

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

    Float değişmez gösterimi ile düzeltebiliriz:

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

    (Not: elbette, genel olarak eşitlik için float veya double sayıları böyle karşılaştırmanız gerekmez )

  3. Doğru aşırı yüklenmiş işlevi çağırmak için (aynı nedenden dolayı):

    Misal:

    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. Cyber'in belirttiği gibi , bir tür kesintisi bağlamında, derleyicinin birfloat :

    Durumda auto:

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

    Ve benzer şekilde, şablon türü kesintisi durumunda:

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

Canlı demo


2
1. noktada 42, otomatik olarak yükseltilen float(ve herhangi bir düzgün derleyicide derleme zamanında gerçekleşecek olan) bir tamsayıdır , dolayısıyla performans cezası yoktur. Muhtemelen şöyle bir şeyi kastettin 42.0.
Matteo Italia

@MatteoItalia, evet 42.0 ofc demek istedim (düzenlendi, teşekkürler)
quantdev

2
@ChristianHackl dönüştürme 4.2için 4.2fayar yan etkisi olabilir FE_INEXACT, bayrak derleyici ve sisteme bağlı olarak ve bazı (kuşkusuz az) programları bu bayrak için kayan nokta operasyonları olmayan kesin, ve hangi bakımı ve testi yapmak . Bu, basit açık derleme zamanı dönüşümünün programın davranışını değiştirdiği anlamına gelir.

6
float foo(float x) { return x*42.0; }tek duyarlıklı bir çarpma için derlenebilir ve en son denediğimde Clang tarafından bu şekilde derlendi. Ancak float foo(float x) { return x*0.1; }, tek bir tek duyarlıklı çarpmaya derlenemez. Bu yamadan önce biraz fazla iyimser olabilirdi, ancak yamadan sonra sonuç her zaman aynı olduğunda dönüşüm-double_precision_op-conversion-double_precision_op-conversion'u single_precision_op'a birleştirmelidir. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq

1
Biri onda biri olan bir değeri hesaplamak isterse someFloat, ifade someFloat * 0.1daha doğru sonuçlar someFloat * 0.1fverirken, çoğu durumda bir kayan nokta bölmesinden daha ucuzdur. Örneğin, (float) (167772208.0f * 0.1), 16777222 yerine 16777220'ye doğru bir şekilde yuvarlayacaktır. Bazı derleyiciler, doublebir kayan nokta bölmesinin yerine bir çarpmanın yerini alabilir , ancak olmayanlar için (tüm değerler olmasa da çoğu için güvenlidir) ) çarpma yararlı bir optimizasyon olabilir, ancak yalnızca doublekarşılıklı olarak yapılırsa .
supercat

22

Değişkeni kayan nokta olarak tanımladığınız için derleyici aşağıdaki değişmez değerlerden herhangi birini yüzer sayıya çevirecektir.

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

Kullandığınız auto(veya başka türden kesinti yöntemleri), örneğin:

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

5
Şablonlar kullanılırken türler de çıkarılır, bu yüzden autotek durum bu değildir.
Shafik Yaghmour

14

Son eki olmayan kayan nokta değişmezleri double türündedir , bu taslak C ++ standart bölümünde 2.14.4 Kayan değişmez değerler ele alınmıştır :

[...] Bir sonek tarafından açıkça belirtilmediği sürece kayan değişmez değerin türü çifttir. [...]

böylece atamak bir hatadır 3.0bir çift değişmezi a şamandıra ?:

float a = 3.0

Hayır, dönüştürülmez, 4.8 Kayan nokta dönüşümleri bölümünde ele alınmaktadır :

Kayan nokta türündeki bir prvalue, başka bir kayan nokta türünün bir pr değerine dönüştürülebilir. Kaynak değeri hedef türünde tam olarak gösterilebiliyorsa, dönüşümün sonucu bu tam temsildir. Kaynak değeri iki bitişik hedef değer arasındaysa, dönüşümün sonucu bu değerlerden herhangi birinin uygulama tanımlı bir seçimidir. Aksi takdirde, davranış tanımsızdır.

GotW # 67'de bunun etkileri hakkında daha fazla ayrıntı okuyabiliriz : çift veya hiçbir şey söyleyen:

Bu, bir çift sabitin örtük olarak (yani sessizce) bir float sabitine dönüştürülebileceği anlamına gelir, bunu yapmak hassasiyeti (yani verileri) kaybetse bile. Bunun C uyumluluğu ve kullanılabilirlik nedenleriyle kalmasına izin verildi, ancak kayan nokta çalışması yaparken aklınızda bulundurmaya değer.

Tanımlanmamış davranış olan bir şey yapmaya çalışırsanız, yani bir float'ın temsil edebileceği minimum değerden küçük veya maksimum değerden daha büyük bir float'a çift miktar koymaya çalışırsanız, kalite derleyicisi sizi uyaracaktır. Gerçekten iyi bir derleyici, tanımlanabilecek, ancak bilgi kaybedebilecek bir şey yapmaya çalışırsanız, yani bir float tarafından temsil edilebilen minimum ve maksimum değerler arasında bir float'a çift miktar koyarsanız, isteğe bağlı bir uyarı sağlayacaktır. tam olarak bir şamandıra olarak temsil edilmelidir.

Bu yüzden genel durum için farkında olmanız gereken uyarılar var.

Pratik bir perspektiften, bu durumda, teknik olarak bir dönüşüm olsa da sonuçlar büyük olasılıkla aynı olacaktır , bunu godbolt üzerinde aşağıdaki kodu deneyerek görebiliriz :

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

ve biz sonuçları görüyoruz func1ve func2ikisini de kullanarak, aynıdır clangve gcc:

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

As Pascal bu yorumunda işaret her zaman bu saymak mümkün olmayacaktır. 0.1Ve 0.1fsırasıyla kullanmak , dönüştürmenin artık açıkça yapılması gerektiğinden, oluşturulan derlemenin farklı olmasına neden olur. Aşağıdaki kod:

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

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

aşağıdaki derlemeyle sonuçlanır:

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

Dönüşümün performans üzerinde bir etkisi olup olmayacağını belirleyip belirlemediğinize bakılmaksızın, doğru türü kullanmak niyetinizi daha iyi belgeler. Örneğin, açık bir dönüşüm kullanmak static_cast, dönüşümün yanlışlıkla değil, bir hatayı veya olası bir hatayı işaret edebilecek şekilde tasarlandığını netleştirmeye yardımcı olur.

Not

Supercat'ın işaret ettiği gibi, örneğin 0.1ile çarpma 0.1feşdeğer değildir. Yorumdan alıntı yapacağım çünkü mükemmeldi ve bir özet muhtemelen bunu hak etmeyecektir:

Örneğin, f 100000224'e eşitse (bu tam olarak bir kayan nokta olarak gösterilebilir), onu onda bir ile çarpmak 10000022'ye yuvarlanan bir sonuç vermelidir, ancak 0.1f ile çarpmak bunun yerine hatalı bir şekilde 10000023'e yuvarlayan bir sonuç verecektir. Eğer on'a bölme niyetindeyseniz, çift sabit 0.1 ile çarpma muhtemelen 10f'ye bölmeden daha hızlı ve 0.1f ile çarpmadan daha kesin olacaktır.

Asıl amacım, başka bir soruda verilen yanlış bir örneği göstermekti, ancak bu, oyuncak örneklerinde ince sorunların olabileceğini iyi bir şekilde gösteriyor.


1
Bu ifadeler dikkati çekiyor olabilir f = f * 0.1;ve f = f * 0.1f; farklı şeyler yapmak . Örneğin, f100000224'e eşitse (ki bu tam olarak a olarak gösterilebilir float), onu onda bir ile çarpmak, 10000022'ye yuvarlanan bir sonuç vermelidir, ancak 0.1f ile çarpmak, bunun yerine yanlışlıkla 10000023'e yuvarlayan bir sonuç verecektir. amaç ona bölmektir, doublesabit 0,1 ile çarpma büyük olasılıkla bölmeden daha hızlı 10fve çarpmadan daha kesin olacaktır 0.1f.
supercat

@supercat Güzel örnek için teşekkür ederim, sizden doğrudan alıntı yaptım, lütfen uygun gördüğünüz şekilde düzenlemekten çekinmeyin.
Shafik Yaghmour

4

Derleyicinin onu reddetmesi anlamında bir hata değil, ancak istediğiniz gibi olmayabileceği anlamında bir hatadır.

Kitabınızın doğru bir şekilde ifade ettiği gibi 3.0, bir tür değeridir double. Dan örtük bir dönüşüm vardouble yefloat , bu yüzdenfloat a = 3.0; bir değişkenin geçerli bir tanımıdır.

Bununla birlikte, en azından kavramsal olarak, bu gereksiz bir dönüşüm gerçekleştirir. Derleyiciye bağlı olarak, dönüştürme derleme zamanında gerçekleştirilebilir veya çalışma süresi için kaydedilebilir. Çalışma zamanı için kaydetmenin geçerli bir nedeni, kayan nokta dönüşümlerinin zor olması ve değer tam olarak gösterilemezse beklenmeyen yan etkilere sahip olabilmesidir ve değerin tam olarak temsil edilip edilemeyeceğini doğrulamak her zaman kolay değildir.

3.0f bu sorunu önler: teknik olarak, derleyicinin çalışma zamanında sabiti hesaplamasına izin verilir (her zaman öyledir), burada, herhangi bir derleyicinin bunu yapması için kesinlikle hiçbir neden yoktur.


Aslında, bir çapraz derleyici söz konusu olduğunda, dönüşümün derleme zamanında gerçekleştirilmesi oldukça yanlış olacaktır, çünkü bu yanlış platformda gerçekleşecektir.
user207421

2

Bir hata olmasa da, biraz özensiz. Bir şamandıra istediğinizi biliyorsunuz, bu yüzden onu bir şamandıra ile başlatın.
Başka bir programcı gelebilir ve bildirimin hangi kısmının, türünün veya başlatıcının doğru olduğundan emin olmayabilir. Neden ikisi de doğru olmasın?
float Cevap = 42.0f;


0

Bir değişken tanımladığınızda, sağlanan başlatıcıyla başlatılır. Bu, başlatıcının değerini başlatılan değişkenin türüne dönüştürmeyi gerektirebilir. Şöyle dediğinizde olan şey budur float a = 3.0;: Başlatıcı değeri dönüştürülür floatve dönüşümün sonucu başlangıç ​​değeri olur a.

Bu genellikle iyidir, ancak yazmaktan zarar gelmez 3.0f ne yaptığınızın farkında olduğunuzu göstermek , özellikle de yazmak istiyorsanız auto a = 3.0f.


0

Aşağıdakileri denerseniz:

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

çıktı alacaksınız:

4:8

32-bit makinede 3.2f boyutu 4 bayt olarak alınır ve 3.2, 32-bit makinede 8 bayt alan çift değer olarak yorumlanır. Bu, aradığınız cevabı sağlamalıdır.


Bu, bunu gösteriyor doubleve floatfarklı, floatbir çift değişmezden
a'yı

Tabii ki, eğer mümkünse veri kesmeye tabi bir çift değerden bir kayan nokta başlatabilirsiniz
Dr.Debasish Jana

4
Evet, biliyorum, ama bu OP'nin sorusuydu, bu yüzden cevabınız, cevabı sağladığını iddia etmenize rağmen cevabınız gerçekten cevap vermedi!
Jonathan Wakely

0

Derleyici, değişmez değerlerden en uygun türü çıkarır veya en uygun olduğunu düşündüğü şeyin en uygun olduğunu düşünür. Bu, hassasiyet yerine verimlilik kaybıdır, yani şamandıra yerine çift kullanın. Şüpheniz varsa, açık hale getirmek için kaşlı ayraçları kullanın:

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

Yazı tipi dönüştürme kurallarının geçerli olduğu başka bir değişkenden başlatırsanız hikaye daha ilginç hale gelir: Bir çift formu bir literal oluşturmak yasal olsa da, olası daraltma olmadan bir int'ten oluşturulamaz:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.