Peringatan C ++: pembagian dua kali lipat dengan nol

98

Kasus 1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

Ini mengkompilasi tanpa peringatan dan cetakan apa pun inf. OK, C ++ dapat menangani pembagian dengan nol, ( lihat langsung ).

Tapi,

Kasus 2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

Kompilator memberikan peringatan berikut ( lihat langsung ):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

Mengapa kompilator memberikan peringatan pada kasus kedua?

Apakah 0 != 0.0?

Edit:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

keluaran:

Same
Jayesh
sumber
9
Saya berasumsi bahwa dibutuhkan nol sebagai bilangan bulat dalam kasus kedua dan memberikan peringatan, bahkan jika kalkulasi akan dilakukan menggunakan dua kali lipat nanti (yang menurut saya seharusnya menjadi perilaku ketika d adalah ganda).
Qubit
10
Ini masalah QoI, sungguh. Baik peringatan atau kurangnya peringatan adalah sesuatu yang diamanatkan oleh standar C ++ itu sendiri. Apakah Anda menggunakan GCC?
StoryTeller - Unslander Monica
5
@StoryTeller Apa itu QoI? en.wikipedia.org/wiki/QoI ?
pengguna202729
5
Sehubungan dengan pertanyaan terakhir Anda, "apakah 0 sama dengan 0,0?" Jawabannya adalah nilainya sama, tetapi seperti yang Anda ketahui, itu tidak berarti keduanya identik. Jenis yang berbeda! Sama seperti 'A' tidak identik dengan 65.
Mr Lister

Jawaban:

108

Pembagian floating point dengan nol didefinisikan dengan baik oleh IEEE dan memberikan tak terbatas (baik positif atau negatif sesuai dengan nilai pembilang (atau NaNuntuk ± 0) ).

Untuk integer, tidak ada cara untuk merepresentasikan infinity dan bahasanya mendefinisikan operasi untuk memiliki perilaku yang tidak terdefinisi sehingga compiler dengan senang hati mencoba untuk mengarahkan Anda keluar dari path tersebut.

Namun dalam kasus ini, karena pembilangnya adalah a double, divisor ( 0) harus dipromosikan menjadi double juga dan tidak ada alasan untuk memberikan peringatan di sini sementara tidak memberikan peringatan, 0.0jadi saya pikir ini adalah bug compiler.

Motti
sumber
8
Keduanya adalah divisi floating point. Dalam d/0, 0diubah menjadi jenis d.
43
Perhatikan bahwa C ++ tidak diperlukan untuk menggunakan IEEE 754 (meskipun saya tidak pernah melihat kompiler menggunakan standar yang berbeda) ..
Yksisarvinen
1
@hvd, poin yang bagus, dalam hal ini ini terlihat seperti bug kompiler
Motti
14
Saya setuju bahwa itu harus memperingatkan dalam kedua kasus, atau tidak memperingatkan dalam kedua kasus (tergantung pada apa yang ditangani kompiler pembagian mengambang oleh nol)
MM
8
Cukup yakin bahwa pembagian floating point dengan nol adalah UB juga - hanya saja GCC yang mengimplementasikannya sesuai dengan IEEE 754. Mereka tidak harus melakukan itu.
Martin Bonner mendukung Monica
42

Dalam Standar C ++, kedua kasus adalah perilaku yang tidak ditentukan . Apa pun dapat terjadi, termasuk memformat hard drive Anda. Anda tidak boleh mengharapkan atau mengandalkan "return inf. Ok" atau perilaku lainnya.

Kompiler tampaknya memutuskan untuk memberikan peringatan dalam satu kasus dan bukan yang lain, tetapi ini tidak berarti bahwa satu kode OK dan yang satu tidak. Ini hanyalah kekhasan dari generasi peringatan yang dibuat oleh kompiler.

Dari standar C ++ 17 [expr.mul] / 4:

/Operator biner menghasilkan hasil bagi, dan %operator biner menghasilkan sisa dari pembagian ekspresi pertama dengan yang kedua. Jika operan kedua dari /atau %nol, perilakunya tidak ditentukan.

MM
sumber
21
Tidak benar, dalam pembagian aritmatika floating point dengan nol didefinisikan dengan baik.
Motti
9
@Motti - Jika seseorang membatasi diri pada standar C ++ saja, tidak ada jaminan seperti itu. Cakupan pertanyaan ini tidak ditentukan dengan baik, terus terang.
StoryTeller - Unslander Monica
9
@StoryTeller Aku cukup yakin (walaupun saya belum melihat dokumen standar sendiri untuk ini) bahwa jika std::numeric_limits<T>::is_iec559ini true, maka pembagian dengan nol untuk Ttidak UB (dan pada kebanyakan platform itu trueuntuk doubledan float, meskipun menjadi portabel Anda akan perlu secara eksplisit memeriksanya dengan ifatau if constexpr).
Daniel H
6
@DanielH - "Reasonable" sebenarnya cukup subjektif. Jika pertanyaan ini diberi tag pengacara-bahasa, akan ada serangkaian asumsi masuk akal lainnya (jauh lebih kecil).
StoryTeller - Unslander Monica
5
@ MM Ingatlah bahwa itu persis seperti yang Anda katakan, tetapi tidak lebih: tidak ditentukan tidak berarti tidak ada persyaratan yang diberlakukan, itu berarti tidak ada persyaratan yang diberlakukan oleh standar . Saya berpendapat bahwa dalam kasus ini, fakta bahwa implementasi didefinisikan is_iec559sebagai truesarana bahwa implementasi mendokumentasikan perilaku yang tidak ditentukan oleh standar. Hanya saja ini adalah salah satu kasus di mana dokumentasi implementasinya dapat dibaca secara terprogram. Bahkan bukan satu-satunya: hal yang sama berlaku is_modulountuk jenis bilangan bulat bertanda.
12

Tebakan terbaik saya untuk menjawab pertanyaan khusus ini adalah bahwa kompiler mengeluarkan peringatan sebelum melakukan konversi intke double.

Jadi, langkah-langkahnya seperti ini:

  1. Ekspresi parse
  2. Operator aritmatika /(T, T2) , di mana T=double, T2=int.
  3. Periksa std::is_integral<T2>::valueadalah truedan b == 0- pemicu peringatan ini.
  4. Keluarkan peringatan
  5. Lakukan konversi implisit T2kedouble
  6. Lakukan pembagian yang terdefinisi dengan baik (karena compiler memutuskan untuk menggunakan IEEE 754).

Ini tentu saja spekulasi dan didasarkan pada spesifikasi yang ditentukan kompiler. Dari sudut pandang standar, kita berurusan dengan kemungkinan Perilaku Tidak Terdefinisi.


Perhatikan bahwa ini adalah perilaku yang diharapkan menurut dokumentasi GCC
(btw. Tampaknya tanda ini tidak dapat digunakan secara eksplisit di GCC 8.1)

-Wdiv-by-zero
Peringatkan tentang pembagian integer waktu kompilasi dengan nol. Ini default. Untuk menghambat pesan peringatan, gunakan -Wno-div-by-zero. Pembagian floating point dengan nol tidak diperingatkan, karena ini bisa menjadi cara yang sah untuk mendapatkan infinitas dan NaN.

Yksisarvinen
sumber
2
Ini bukanlah cara kerja kompiler C ++. Kompiler harus melakukan resolusi kelebihan beban /untuk mengetahui bahwa pembagiannya. Jika sisi kiri akan menjadi sebuah Fooobjek, dan akan ada a operator/(Foo, int), maka itu mungkin bukan pembagian. Kompilator hanya mengetahui pembagiannya ketika ia telah memilih built-in / (double, double)menggunakan konversi implisit dari sisi kanan. Tapi itu berarti BUKAN melakukan pembagian dengan int(0), itu melakukan pembagian dengan double(0).
MSalters
@MSalters Silakan lihat ini. Pengetahuan saya tentang C ++ memang terbatas, tapi menurut referensi operator /(double, int)tentu bisa diterima. Kemudian, dikatakan bahwa konversi dilakukan sebelum tindakan lain, tetapi GCC dapat memeras pemeriksaan cepat jika T2berjenis integer dan b == 0dan mengeluarkan peringatan jika demikian. Tidak yakin apakah itu sepenuhnya sesuai standar, tetapi penyusun memiliki kebebasan penuh dalam menentukan peringatan dan kapan mereka harus dipicu.
Yksisarvinen
2
Kita berbicara tentang operator bawaan di sini. Itu lucu. Ini sebenarnya bukan fungsi, jadi Anda tidak bisa mengetahui alamatnya. Oleh karena itu, Anda tidak dapat menentukan apakah operator/(double,int)benar-benar ada. Kompilator dapat misalnya memutuskan untuk mengoptimalkan a/bkonstanta bdengan menggantinya dengan a * (1/b). Tentu saja, itu berarti Anda tidak lagi menelepon operator/(double,double)pada saat runtime tetapi semakin cepat operator*(double,double). Tapi sekarang pengoptimal yang 1/0operator*
tersandung
@MSalters Umumnya pembagian floating point tidak dapat diganti dengan perkalian, mungkin terlepas dari kasus luar biasa, seperti 2.
user202729
2
@ user202729: GCC melakukannya bahkan untuk divisi integer . Biarkan yang tenggelam di sejenak. GCC menggantikan pembagian integer dengan perkalian integer. Ya, itu mungkin, karena GCC tahu itu beroperasi di atas ring (nomor modulo 2 ^ N)
MSalters
9

Saya tidak akan masuk ke UB / bukan UB bencana di jawaban ini.

Saya hanya ingin menunjukkan itu 0dan 0.0 berbeda meskipun 0 == 0.0menilai benar. 0adalah intliteral dan 0.0merupakan doubleliteral.

Namun dalam kasus ini, hasil akhirnya sama: d/0adalah pembagian floating point karena dganda dan 0secara implisit diubah menjadi double.

bolov
sumber
5
Saya tidak melihat bagaimana ini relevan, mengingat bahwa konversi aritmatika biasa menentukan bahwa membagi a doubledengan intcara yang intdikonversi double , dan ditentukan dalam standar yang 0dikonversi menjadi 0.0 (konv.fpint / 2)
MM
@MM OP ingin tahu apakah 0sama dengan0.0
bolov
2
Pertanyaannya mengatakan "Apakah 0 != 0.0?". OP tidak pernah menanyakan apakah mereka "sama". Juga bagi saya tampaknya maksud dari pertanyaan tersebut adalah untuk dilakukan dengan apakah d/0dapat berperilaku berbedad/0.0
MM
2
@ MM - OP memang bertanya . Mereka tidak benar-benar menunjukkan netiket SO yang layak dengan pengeditan konstanta tersebut.
StoryTeller - Unslander Monica
7

Saya berpendapat bahwa foo/0dan foo/0.0yang tidak sama. Yakni, efek yang dihasilkan dari yang pertama (pembagian integer atau pembagian floating point) sangat tergantung pada jenisnya foo, sedangkan hal yang sama tidak berlaku untuk yang kedua (itu akan selalu menjadi pembagian floating point).

Apakah salah satu dari keduanya adalah UB tidak relevan. Mengutip standar:

Perilaku tidak terdefinisi yang diizinkan berkisar dari mengabaikan situasi sepenuhnya dengan hasil yang tidak dapat diprediksi, hingga berperilaku selama penerjemahan atau eksekusi program dengan cara yang terdokumentasi, karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik) , hingga menghentikan terjemahan atau eksekusi (dengan penerbitan dari pesan diagnostik).

(Penekanan saya)

Pertimbangkan peringatan " sarankan tanda kurung di sekitar tugas yang digunakan sebagai nilai kebenaran ": Cara untuk memberi tahu compiler bahwa Anda benar - benar ingin menggunakan hasil tugas adalah dengan menjadi eksplisit, dan menambahkan tanda kurung di sekitar tugas. Pernyataan yang dihasilkan memiliki efek yang sama, tetapi memberi tahu kompiler bahwa Anda tahu apa yang Anda lakukan. Hal yang sama dapat dikatakan tentang foo/0.0: Karena Anda secara eksplisit memberi tahu compiler "Ini adalah pembagian floating point" dengan menggunakan 0.0alih-alih 0, kompilator mempercayai Anda dan tidak akan mengeluarkan peringatan.

Cássio Renan
sumber
1
Keduanya harus menjalani konversi aritmatika biasa untuk membuatnya menjadi tipe umum, yang akan membuat kedua kasus pembagian floating point.
Shafik Yaghmour
@ShafikYaghmour Anda melewatkan poin dalam jawaban. Perhatikan bahwa saya tidak pernah menyebutkan apa jenisnya foo. Itu disengaja. Penegasan Anda hanya benar dalam kasus itu fooadalah tipe titik mengambang.
Cássio Renan
Saya tidak, kompilator memiliki informasi tipe dan memahami konversi, mungkin penganalisis statis berbasis teks dapat ditangkap oleh hal-hal seperti itu tetapi kompilator tidak boleh.
Shafik Yaghmour
Maksud saya adalah ya, kompiler mengetahui konversi aritmatika biasa, tetapi memilih untuk tidak mengeluarkan peringatan ketika programmer sedang eksplisit. Intinya adalah ini mungkin bukan bug, melainkan perilaku yang disengaja.
Cássio Renan
Kemudian dokumentasi yang saya tunjuk tidak benar, karena kedua kasus tersebut merupakan pembagian floating-point. Jadi, dokumentasinya salah atau diagnostiknya memiliki bug.
Shafik Yaghmour
4

Ini terlihat seperti bug gcc, dokumentasinya dengan -Wno-div-by-zero jelas mengatakan :

Jangan memperingatkan tentang pembagian integer waktu kompilasi dengan nol. Pembagian floating-point dengan nol tidak diperingatkan, karena ini bisa menjadi cara yang sah untuk mendapatkan infinitas dan NaN.

dan setelah konversi aritmatika biasa yang tercakup dalam [expr.arith.conv] kedua operan akan menjadi dua kali lipat :

Banyak operator biner yang mengharapkan operan aritmatika atau tipe enumerasi menyebabkan konversi dan menghasilkan tipe hasil dengan cara yang sama. Tujuannya adalah untuk menghasilkan jenis yang sama, yang juga merupakan jenis hasil. Pola ini disebut konversi aritmatika biasa, yang didefinisikan sebagai berikut:

...

- Jika tidak, jika salah satu operand adalah double, operand lainnya harus diubah menjadi double.

dan [expr.mul] :

Operan dari * dan / harus memiliki jenis pencacahan aritmatika atau tidak terbatas; operan dari% harus memiliki tipe pencacahan integral atau tidak tercakup. Konversi aritmatika biasa dilakukan pada operan dan menentukan jenis hasilnya.

Sehubungan dengan apakah floating point dibagi nol adalah perilaku tidak terdefinisi dan bagaimana implementasi yang berbeda menghadapinya, tampaknya jawaban saya di sini . TL; DR; Sepertinya gcc sesuai dengan Lampiran F wrt ke floating point dibagi nol, jadi tidak ditentukan tidak berperan di sini. Jawabannya akan berbeda untuk dentang.

Shafik Yaghmour
sumber
2

Pembagian floating point dengan nol berperilaku berbeda dari pembagian integer dengan nol.

The IEEE floating point membedakan standar antara inf + dan -inf, sementara bilangan bulat tidak bisa menyimpan tak terhingga. Pembagian bilangan bulat dengan hasil nol adalah perilaku yang tidak terdefinisi. Pembagian floating point dengan nol ditentukan oleh standar floating point dan menghasilkan + inf atau -inf.

Rizwan
sumber
2
Ini benar tetapi tidak jelas bagaimana ini relevan dengan pertanyaan, karena pembagian floating point dilakukan dalam kedua kasus . Tidak ada pembagian integer dalam kode OP.
Konrad Rudolph