Apakah 'float a = 3.0;' pernyataan yang benar?

86

Jika saya memiliki pernyataan berikut:

float a = 3.0 ;

apakah itu kesalahan? Saya membaca di sebuah buku yang 3.0memiliki doublenilai dan saya harus menentukannya sebagai float a = 3.0f. Begitu?

TESLA____
sumber
2
Kompiler akan mengubah literal ganda 3.0menjadi float untuk Anda. Hasil akhirnya tidak bisa dibedakan float a = 3.0f.
David Heffernan
6
@EdHeal: Ya, tetapi tidak terlalu relevan dengan pertanyaan ini, yaitu tentang aturan C ++.
Keith Thompson
20
Yah, setidaknya Anda membutuhkan ;setelah.
Hot Licks
3
10 suara negatif dan tidak banyak di komentar untuk menjelaskannya, sangat mengecewakan. Ini adalah pertanyaan pertama OP dan jika orang merasa ini layak untuk 10 suara negatif, harus ada beberapa penjelasan. Ini adalah pertanyaan yang valid dengan implikasi yang tidak jelas dan banyak hal menarik untuk dipelajari dari jawaban dan komentar.
Shafik Yaghmour
3
@HotLicks ini bukan tentang perasaan buruk atau baik, yakin itu mungkin tampak tidak adil tapi itulah hidup, bagaimanapun juga itu adalah poin unicorn. Dowvote tentunya bukan untuk membatalkan suara positif yang tidak Anda sukai seperti suara positif tidak untuk membatalkan suara negatif yang tidak Anda sukai. Jika orang merasa pertanyaannya bisa diperbaiki, tentunya penanya yang baru pertama kali harus mendapatkan umpan balik. Saya tidak melihat alasan apa pun untuk tidak memilih tetapi saya ingin tahu mengapa orang lain melakukannya meskipun mereka bebas untuk tidak mengatakannya.
Shafik Yaghmour

Jawaban:

159

Ini bukan kesalahan untuk dideklarasikan float a = 3.0: jika Anda melakukannya, kompilator akan mengubah double literal 3.0 menjadi float untuk Anda.


Namun, Anda harus menggunakan notasi literal float dalam skenario tertentu.

  1. Untuk alasan kinerja:

    Secara khusus, pertimbangkan:

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

    Di sini kompilator akan mengeluarkan konversi (yang akan Anda bayarkan saat runtime) untuk setiap nilai yang dikembalikan. Untuk menghindarinya, Anda harus menyatakan:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. Untuk menghindari bug saat membandingkan hasil:

    misalnya perbandingan berikut gagal:

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

    Kita bisa memperbaikinya dengan notasi literal float:

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

    (Catatan: tentu saja, ini bukan cara Anda membandingkan angka float atau double untuk persamaan secara umum )

  3. Untuk memanggil fungsi kelebihan beban yang benar (untuk alasan yang sama):

    Contoh:

    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. Seperti dicatat oleh Cyber , dalam konteks deduksi tipe, diperlukan untuk membantu kompilator menyimpulkan float:

    Dalam kasus auto:

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

    Dan serupa, dalam kasus pengurangan jenis template:

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

Demo langsung

quantdev
sumber
2
Pada poin 1 42adalah integer, yang secara otomatis dipromosikan menjadi float(dan itu akan terjadi pada waktu kompilasi di kompiler yang layak), jadi tidak ada penalti performa. Mungkin maksudmu seperti itu 42.0.
Matteo Italia
@MatteoItalia, ya maksud saya 42.0 ofc (diedit, terima kasih)
quantdev
2
Mengonversi @ChristianHackl 4.2ke 4.2fmungkin memiliki efek samping menyetel FE_INEXACTflag, bergantung pada compiler dan sistem, dan beberapa (memang sedikit) program peduli tentang operasi floating-point mana yang tepat, dan mana yang tidak, dan menguji flag itu . Ini berarti bahwa transformasi waktu kompilasi yang jelas dan sederhana mengubah perilaku program.
6
float foo(float x) { return x*42.0; }dapat disusun menjadi perkalian presisi tunggal, dan dikompilasi oleh Clang terakhir kali saya mencoba. Namun float foo(float x) { return x*0.1; }tidak dapat dikompilasi menjadi perkalian presisi tunggal. Mungkin sedikit terlalu optimis sebelum tambalan ini, tetapi setelah tambalan itu seharusnya hanya menggabungkan konversi-double_precision_op-conversion ke single_precision_op ketika hasilnya selalu sama. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq
1
Jika seseorang ingin menghitung nilai yang sepersepuluh someFloat, ekspresi tersebut someFloat * 0.1akan menghasilkan hasil yang lebih akurat daripada someFloat * 0.1f, sementara dalam banyak kasus lebih murah daripada pembagian floating-point. Misalnya, (float) (167772208.0f * 0.1) akan dibulatkan dengan benar ke 16777220 daripada 16777222. Beberapa kompiler mungkin mengganti doubleperkalian untuk pembagian floating-point, tetapi untuk yang tidak (aman untuk banyak meskipun tidak semua nilai ) penggandaan mungkin merupakan pengoptimalan yang berguna, tetapi hanya jika dilakukan dengan doubletimbal balik.
supercat
22

Kompilator akan mengubah salah satu literal berikut menjadi float, karena Anda mendeklarasikan variabel sebagai float.

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

Ini akan menjadi masalah jika Anda menggunakan auto(atau metode pengurangan jenis lainnya), misalnya:

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float
Cory Kramer
sumber
5
Jenis juga disimpulkan saat menggunakan templat, jadi autobukan satu-satunya kasus.
Shafik Yaghmour
14

Literal floating point tanpa sufiks adalah tipe double , ini tercakup dalam draf bagian standar C ++ 2.14.4 Literal mengambang :

[...] Jenis literal mengambang adalah ganda kecuali secara eksplisit ditentukan oleh sufiks. [...]

jadi itu kesalahan untuk menetapkan 3.0suatu literal ganda untuk mengapung ?:

float a = 3.0

Tidak, tidak, itu akan dikonversi, yang dibahas di bagian 4.8 Konversi titik mengambang :

Nilai pr dari jenis titik mengambang dapat diubah menjadi nilai pr dari jenis titik mengambang lainnya. Jika nilai sumber dapat secara tepat direpresentasikan dalam jenis tujuan, hasil konversinya adalah representasi yang tepat. Jika nilai sumber berada di antara dua nilai tujuan yang berdekatan, hasil konversi adalah pilihan yang ditentukan penerapan dari salah satu nilai tersebut. Jika tidak, perilakunya tidak ditentukan.

Kami dapat membaca lebih detail tentang implikasi dari ini di GotW # 67: ganda atau tidak ada yang mengatakan:

Ini berarti bahwa konstanta ganda dapat secara implisit (yaitu, diam-diam) diubah menjadi konstanta float, bahkan jika hal tersebut kehilangan presisi (yaitu, data). Ini diizinkan untuk tetap karena kompatibilitas C dan alasan kegunaan, tetapi perlu diingat saat Anda melakukan pekerjaan floating-point.

Kompiler kualitas akan memperingatkan Anda jika Anda mencoba melakukan sesuatu yang perilaku tidak terdefinisi, yaitu menempatkan kuantitas ganda ke dalam float yang kurang dari nilai minimum, atau lebih besar dari maksimum, yang dapat diwakili oleh float. Kompiler yang sangat baik akan memberikan peringatan opsional jika Anda mencoba melakukan sesuatu yang mungkin didefinisikan tetapi dapat kehilangan informasi, yaitu menempatkan kuantitas ganda ke dalam pelampung yang berada di antara nilai minimum dan maksimum yang dapat direpresentasikan oleh pelampung, tetapi yang tidak dapat direpresentasikan persis sebagai pelampung.

Jadi ada peringatan untuk kasus umum yang harus Anda waspadai.

Dari segi kepraktisan, dalam hal ini kemungkinan besar hasilnya akan sama walaupun secara teknis ada konversi, kita bisa melihatnya dengan mencoba kode berikut pada 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;
}

dan kami melihat bahwa hasil untuk func1dan func2identik, menggunakan keduanya clangdan gcc:

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

Seperti yang ditunjukkan Pascal dalam komentar ini, Anda tidak akan selalu dapat mengandalkan ini. Menggunakan 0.1dan 0.1fmasing - masing menyebabkan perakitan yang dihasilkan berbeda karena konversi sekarang harus dilakukan secara eksplisit. Kode berikut:

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

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

hasil perakitan berikut:

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

Terlepas dari apakah Anda dapat menentukan apakah konversi akan berdampak pada kinerja atau tidak, menggunakan jenis dokumen yang benar lebih baik untuk tujuan Anda. Menggunakan konversi eksplisit misalnya static_castjuga membantu untuk memperjelas bahwa konversi itu dimaksudkan bukan tidak disengaja, yang mungkin menandakan bug atau potensi bug.

Catatan

Seperti yang ditunjukkan supercat, perkalian dengan eg 0.1dan 0.1ftidak ekivalen. Saya hanya akan mengutip komentar karena itu sangat bagus dan ringkasannya mungkin tidak akan adil:

Misalnya, jika f sama dengan 100000224 (yang dapat direpresentasikan secara tepat sebagai pelampung), mengalikannya dengan sepersepuluh akan menghasilkan hasil yang membulatkan ke bawah menjadi 10000022, tetapi mengalikan dengan 0,1f malah akan menghasilkan hasil yang keliru membulatkan menjadi 10000023 Jika tujuannya adalah untuk membagi dengan sepuluh, perkalian dengan konstanta ganda 0,1 kemungkinan akan lebih cepat daripada pembagian dengan 10f, dan lebih tepat daripada perkalian dengan 0,1f.

Poin awal saya adalah untuk mendemonstrasikan contoh palsu yang diberikan dalam pertanyaan lain tetapi ini menunjukkan masalah halus dapat ada dalam contoh mainan.

Shafik Yaghmour
sumber
1
Mungkin perlu dicatat bahwa ekspresi f = f * 0.1;dan f = f * 0.1f; melakukan hal yang berbeda . Misalnya, jika fsama dengan 100000224 (yang dapat direpresentasikan secara persis sebagai a float), mengalikannya dengan sepersepuluh akan menghasilkan hasil yang dibulatkan menjadi 10000022, tetapi mengalikan dengan 0,1f akan menghasilkan hasil yang keliru membulatkan menjadi 10000023. Jika tujuannya adalah untuk membagi dengan sepuluh, perkalian dengan doublekonstanta 0,1 kemungkinan akan lebih cepat daripada pembagian dengan 10f, dan lebih tepat daripada perkalian dengan 0.1f.
supercat
@supercat terima kasih atas contoh yang bagus, saya mengutip Anda secara langsung, silakan edit sesuai keinginan Anda.
Shafik Yaghmour
4

Ini bukan kesalahan dalam artian kompilator akan menolaknya, tetapi ini adalah kesalahan dalam artian mungkin bukan itu yang Anda inginkan.

Seperti yang dinyatakan buku Anda dengan benar, 3.0adalah nilai tipe double. Ada konversi implisit dari doublemenjadi float, begitu float a = 3.0;juga definisi variabel yang valid.

Namun, setidaknya secara konseptual, ini melakukan konversi yang tidak perlu. Bergantung pada kompilernya, konversi dapat dilakukan pada waktu kompilasi, atau mungkin disimpan untuk run time. Alasan yang valid untuk menyimpannya untuk waktu proses adalah bahwa konversi floating-point sulit dan mungkin memiliki efek samping yang tidak terduga jika nilai tidak dapat direpresentasikan dengan tepat, dan tidak selalu mudah untuk memverifikasi apakah nilai dapat direpresentasikan dengan tepat.

3.0f menghindari masalah itu: meskipun secara teknis, kompilator masih diperbolehkan menghitung konstanta pada waktu proses (selalu demikian), di sini, sama sekali tidak ada alasan mengapa kompilator mungkin melakukan itu.


sumber
Memang, dalam kasus kompilator silang, akan sangat tidak tepat jika konversi dilakukan pada waktu kompilasi, karena itu akan terjadi pada platform yang salah.
pengguna207421
2

Meskipun bukan merupakan kesalahan, namun itu sedikit ceroboh. Anda tahu Anda menginginkan pelampung, jadi inisialisasi dengan pelampung.
Programmer lain mungkin datang dan tidak yakin bagian mana dari deklarasi yang benar, jenis atau penginisialisasi. Mengapa keduanya tidak benar?
mengapung Jawaban = 42.0f;

Insinyur
sumber
0

Ketika Anda mendefinisikan variabel, itu diinisialisasi dengan penginisialisasi yang disediakan. Ini mungkin memerlukan konversi nilai penginisialisasi ke jenis variabel yang sedang diinisialisasi. Itulah yang terjadi ketika Anda mengatakan float a = 3.0;: Nilai penginisialisasi dikonversi menjadi float, dan hasil konversi menjadi nilai awal a.

Biasanya tidak masalah, tetapi tidak ada salahnya menulis 3.0funtuk menunjukkan bahwa Anda sadar akan apa yang Anda lakukan, dan terutama jika Anda ingin menulis auto a = 3.0f.

Kerrek SB
sumber
0

Jika Anda mencoba yang berikut ini:

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

Anda akan mendapatkan keluaran sebagai:

4:8

yang menunjukkan, ukuran 3.2f diambil sebagai 4 byte pada mesin 32-bit dimana 3,2 diartikan sebagai nilai ganda yang mengambil 8 byte pada mesin 32-bit. Ini harus memberikan jawaban yang Anda cari.

Dr Debasish Jana
sumber
Itu menunjukkan bahwa doubledan floatberbeda, itu tidak menjawab apakah Anda dapat menginisialisasi a floatdari literal ganda
Jonathan Wakely
tentu saja Anda dapat menginisialisasi pelampung dari nilai ganda yang tunduk pada pemotongan data, jika berlaku
Dr. Debasish Jana
4
Ya saya tahu, tapi itu pertanyaan OP, jadi jawaban Anda gagal menjawabnya, meskipun mengaku memberikan jawabannya!
Jonathan Wakely
0

Penyusun menyimpulkan jenis yang paling pas dari literal, atau setidaknya apa yang menurutnya paling pas. Artinya agak kehilangan efisiensi atas presisi, yaitu menggunakan double sebagai pengganti float. Jika ragu, gunakan brace-intializers untuk membuatnya eksplisit:

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

Ceritanya menjadi lebih menarik jika Anda menginisialisasi dari variabel lain di mana aturan konversi tipe berlaku: Meskipun legal untuk menyusun bentuk ganda literal, itu tidak dapat dikonstruksi dari int tanpa kemungkinan penyempitan:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
truschival
sumber