คำเตือน C ++: การหารสองด้วยศูนย์


98

กรณีที่ 1:

#include <iostream>

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

มันรวบรวมโดยไม่มีคำเตือนใด ๆ infและภาพพิมพ์ ตกลง C ++ สามารถจัดการการหารด้วยศูนย์ได้ ( ดูสด )

แต่,

กรณีที่ 2:

#include <iostream>

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

คอมไพเลอร์ให้คำเตือนต่อไปนี้ ( ดูสด ):

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

เหตุใดคอมไพเลอร์จึงแจ้งเตือนในกรณีที่สอง

คือ0 != 0.0?

แก้ไข:

#include <iostream>

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

เอาต์พุต:

Same

9
ฉันคิดว่ามันใช้ศูนย์เป็นจำนวนเต็มในกรณีที่สองและลดคำเตือนแม้ว่าการคำนวณจะทำโดยใช้ double ในภายหลัง (ซึ่งฉันคิดว่าควรเป็นพฤติกรรมเมื่อ d เป็นสองเท่า)
Qubit

10
มันเป็นปัญหา QoI จริงๆ ทั้งคำเตือนหรือการไม่มีคำเตือนเป็นสิ่งที่กำหนดโดยมาตรฐาน C ++ เอง คุณใช้ GCC หรือไม่?
StoryTeller - Unslander Monica


5
@StoryTeller QoI คืออะไร? en.wikipedia.org/wiki/QoI ?
user202729

5
สำหรับคำถามสุดท้ายของคุณ "0 เท่ากับ 0.0 หรือไม่" คำตอบคือค่าเหมือนกัน แต่อย่างที่คุณพบนั่นไม่ได้หมายความว่าค่าเหล่านี้เหมือนกัน ประเภทต่างๆ! เช่นเดียวกับ 'A' ไม่เหมือนกับ 65.
Mr Lister

คำตอบ:


108

การแบ่งจุดลอยตัวด้วยศูนย์ถูกกำหนดโดย IEEEและให้อินฟินิตี้ (ทั้งบวกหรือลบตามค่าของตัวเศษ(หรือNaNสำหรับ± 0 )

สำหรับจำนวนเต็มไม่มีวิธีใดที่จะแสดงถึงอินฟินิตี้และภาษาจะกำหนดการดำเนินการให้มีลักษณะการทำงานที่ไม่ได้กำหนดไว้ดังนั้นคอมไพเลอร์จะพยายามคัดท้ายคุณอย่างเป็นประโยชน์จากเส้นทางนั้น

อย่างไรก็ตามในกรณีนี้เนื่องจากตัวเศษเป็น a doubleตัวหาร ( 0) ควรได้รับการเลื่อนตำแหน่งเป็นสองเท่าด้วยและไม่มีเหตุผลที่จะแจ้งเตือนที่นี่ในขณะที่ไม่ได้แจ้งเตือน0.0ดังนั้นฉันจึงคิดว่านี่เป็นบั๊กของคอมไพเลอร์


8
ทั้งสองเป็นหน่วยงานลอยตัว ในd/0, จะถูกแปลงเป็นประเภทของ0 d

43
โปรดทราบว่า C ++ ไม่จำเป็นต้องใช้ IEEE 754 (แม้ว่าฉันจะไม่เคยเห็นคอมไพเลอร์ที่ใช้มาตรฐานอื่น
ก็ตาม

1
@hvd จุดดีกรณีนี้ดูเหมือนบั๊กคอมไพเลอร์
Motti

14
ฉันยอมรับว่าควรเตือนในทั้งสองกรณีหรือไม่เตือนในทั้งสองกรณี (ขึ้นอยู่กับว่าคอมไพเลอร์จัดการการหารลอยด้วยศูนย์คืออะไร)
MM

8
ค่อนข้างแน่ใจว่าการแบ่งจุดลอยตัวด้วยศูนย์คือ UB เช่นกัน GCC ดำเนินการตาม IEEE 754 เท่านั้นพวกเขาไม่จำเป็นต้องทำเช่นนั้น
Martin Bonner สนับสนุน Monica

42

ใน c ++ มาตรฐานทั้งกรณีที่มีพฤติกรรมที่ไม่ได้กำหนด อาจเกิดอะไรขึ้นรวมถึงการฟอร์แมตฮาร์ดไดรฟ์ของคุณ คุณไม่ควรคาดหวังหรือพึ่งพา "return inf. ok" หรือพฤติกรรมอื่นใด

เห็นได้ชัดว่าคอมไพเลอร์ตัดสินใจที่จะให้คำเตือนในกรณีหนึ่งไม่ใช่อีกกรณีหนึ่ง แต่นี่ไม่ได้หมายความว่าหนึ่งรหัสนั้นใช้ได้และอีกรหัสหนึ่งไม่ได้ มันเป็นเพียงมุมกลับของการสร้างคำเตือนของคอมไพเลอร์

จากมาตรฐาน C ++ 17 [expr.mul] / 4:

ตัว/ดำเนินการไบนารีจะให้ผลหารและตัว%ดำเนินการไบนารีจะให้ผลที่เหลือจากการหารนิพจน์แรกด้วยตัวที่สอง ถ้าตัวถูกดำเนินการที่สอง/หรือ%เป็นศูนย์พฤติกรรมจะไม่ได้กำหนดไว้


21
ไม่เป็นความจริงในการหารเลขคณิตของจุดลอยตัวด้วยศูนย์นั้นถูกกำหนดไว้อย่างดี
Motti

9
@Motti - หากใคร จำกัด ตัวเองไว้ที่มาตรฐาน C ++ เพียงอย่างเดียวจะไม่มีการรับประกันดังกล่าว ขอบเขตของคำถามนี้ไม่ได้ระบุไว้อย่างชัดเจนเพื่อให้ตรงไปตรงมา
StoryTeller - Unslander Monica

9
@StoryTeller ฉันค่อนข้างแน่ใจ (แม้ว่าฉันจะไม่ได้ดูเอกสารมาตรฐานสำหรับเรื่องนี้) ว่าถ้าstd::numeric_limits<T>::is_iec559เป็นtrueเช่นนั้นการหารด้วยศูนย์Tไม่ใช่ UB (และบนแพลตฟอร์มส่วนใหญ่มีไว้trueสำหรับdoubleและfloatแม้ว่าจะพกพาได้ จำเป็นต้องตรวจสอบอย่างชัดเจนด้วยifหรือif constexpr)
Daniel H

6
@DanielH - "สมเหตุสมผล" จริงๆแล้วค่อนข้างเป็นเรื่องส่วนตัว หากคำถามนี้ถูกแท็กทนายความภาษาก็จะมีสมมติฐานที่สมเหตุสมผลอีกชุดหนึ่ง (เล็กกว่ามาก)
StoryTeller - Unslander Monica

5
@MM โปรดจำไว้ว่ามันเป็นสิ่งที่คุณกล่าวว่า แต่ไม่มีอะไรเพิ่มเติม: undefined ไม่ได้หมายความว่าต้องการจะไม่มีการเรียกเก็บก็หมายความว่าต้องการไม่มีกำหนดโดยมาตรฐาน ฉันจะยืนยันว่าในกรณีนี้ความจริงที่ว่ากำหนดดำเนินงานis_iec559เป็นtrueวิธีการที่ดำเนินการอยู่ในการจัดเก็บเอกสารพฤติกรรมที่ใบไม่ได้กำหนดมาตรฐาน นี่เป็นเพียงกรณีเดียวที่สามารถอ่านเอกสารของการนำไปใช้งานโดยใช้โปรแกรมได้ ไม่ใช่แม้แต่คนเดียว: เช่นเดียวกันกับis_moduloประเภทจำนวนเต็มที่ลงนาม

13

เดาที่ดีที่สุดของฉันที่จะตอบคำถามนี้โดยเฉพาะอย่างยิ่งจะเป็นที่ปล่อยออกมาคอมไพเลอร์เตือนก่อนที่จะดำเนินการแปลงไปintdouble

ดังนั้นขั้นตอนจะเป็นดังนี้:

  1. แยกวิเคราะห์นิพจน์
  2. ผู้ประกอบการทางคณิตศาสตร์ /(T, T2)ที่,T=doubleT2=int
  3. ตรวจสอบว่าstd::is_integral<T2>::valueเป็นtrueและb == 0- สิ่งนี้ทำให้เกิดคำเตือน
  4. ส่งเสียงเตือน
  5. ทำการแปลงโดยนัยของT2เป็นdouble
  6. ทำการแบ่งส่วนที่กำหนดไว้อย่างดี (เนื่องจากคอมไพเลอร์ตัดสินใจใช้ IEEE 754)

นี่เป็นการคาดเดาแน่นอนและเป็นไปตามข้อกำหนดที่คอมไพเลอร์กำหนด จากมุมมองมาตรฐานเรากำลังจัดการกับพฤติกรรมที่ไม่ได้กำหนดที่เป็นไปได้


โปรดทราบว่านี่เป็นพฤติกรรมที่คาดไว้ตามเอกสาร GCC
(btw ดูเหมือนว่าไม่สามารถใช้แฟล็กนี้อย่างชัดเจนใน GCC 8.1)

-Wdiv-by-zero
เตือนเกี่ยวกับการหารจำนวนเต็มเวลาคอมไพล์ด้วยศูนย์ ค่านี้เป็นค่าเริ่มต้น ในการยับยั้งข้อความเตือนให้ใช้ -Wno-div-by-zero การแบ่งจุดลอยตัวด้วยศูนย์ไม่ได้รับการเตือนเนื่องจากอาจเป็นวิธีที่ถูกต้องในการได้รับ infinities และ NaNs


2
นี่ไม่ใช่วิธีการทำงานของคอมไพเลอร์ C ++ คอมไพเลอร์ต้องดำเนินการแก้ปัญหาโอเวอร์โหลด/เพื่อให้ทราบว่าเป็นการแบ่งส่วน ถ้าด้านซ้ายมือจะเป็นFooวัตถุและจะมีวัตถุอยู่แสดงoperator/(Foo, int)ว่าอาจจะไม่มีการแบ่ง คอมไพเลอร์จะรู้ก็ต่อเมื่อมีการเลือกbuilt-in / (double, double)โดยใช้การแปลงทางขวามือโดยปริยาย แต่นั่นหมายความว่ามันจะไม่ได้ทำส่วนโดยการก็ทำหารด้วยint(0) double(0)
MSalters

@MSalters โปรดดูสิ่งนี้ ความรู้ของฉันเกี่ยวกับ C ++ มี จำกัด แต่ตามการอ้างอิงoperator /(double, int)นั้นเป็นที่ยอมรับอย่างแน่นอน จากนั้นกล่าวว่าการแปลงจะดำเนินการก่อนการดำเนินการอื่น ๆ แต่ GCC สามารถบีบการตรวจสอบอย่างรวดเร็วว่าT2เป็นประเภทจำนวนเต็มหรือไม่b == 0และจะส่งเสียงเตือนหากเป็นเช่นนั้น ไม่แน่ใจว่าเป็นไปตามมาตรฐานทั้งหมดหรือไม่ แต่คอมไพเลอร์มีอิสระอย่างเต็มที่ในการกำหนดคำเตือนและเวลาที่ควรเรียกใช้
Yksisarvinen

2
เรากำลังพูดถึงตัวดำเนินการในตัวที่นี่ เป็นเรื่องตลก มันไม่ใช่ฟังก์ชั่นดังนั้นคุณไม่สามารถใช้ที่อยู่ของมันได้ ดังนั้นคุณจึงไม่สามารถระบุได้ว่าoperator/(double,int)มีอยู่จริงหรือไม่ ตัวอย่างเช่นคอมไพเลอร์อาจตัดสินใจที่จะปรับให้เหมาะสมa/bสำหรับค่าคงที่bโดยแทนที่ด้วยa * (1/b). แน่นอนนั่นหมายความว่าคุณจะไม่เรียกoperator/(double,double)ที่รันไทม์ operator*(double,double)แต่ได้เร็วขึ้น แต่ตอนนี้เป็นเครื่องมือเพิ่มประสิทธิภาพที่เดินทางไป1/0แล้วค่าคงที่จะต้องป้อนoperator*
MSalters

@MSalters โดยทั่วไปการหารทศนิยมไม่สามารถแทนที่ด้วยการคูณได้อาจจะนอกเหนือจากกรณีพิเศษเช่น 2
user202729

2
@ user202729: GCC ทำแม้กระทั่งการหารจำนวนเต็ม ปล่อยให้มันจมลงไปสักครู่ GCC แทนที่การหารจำนวนเต็มด้วยการคูณจำนวนเต็ม ใช่เป็นไปได้เพราะ GCC รู้ว่ามันทำงานบนวงแหวน (ตัวเลขโมดูโล 2 ^ N)
MSalters

9

ฉันจะไม่เข้าไปใน UB / ไม่ UB debacle ในคำตอบนี้

ผมแค่อยากจะจุดนั้น0และ0.0 จะแตกต่างกันแม้จะมี0 == 0.0การประเมินให้เป็นจริง 0เป็นintตัวอักษรและ0.0เป็นdoubleตัวอักษร

อย่างไรก็ตามในกรณีนี้ผลลัพธ์จะเหมือนกัน: d/0คือการหารทศนิยมเนื่องจากdเป็นสองเท่าจึง0ถูกแปลงโดยปริยายเป็นสองเท่า


5
ฉันไม่เห็นว่าสิ่งนี้เกี่ยวข้องอย่างไรเนื่องจากการแปลงเลขคณิตตามปกติระบุว่าการหาร a doubleด้วยintวิธีที่intแปลงเป็นdouble และระบุไว้ในมาตรฐานที่0แปลงเป็น0.0 (Conv.fpint / 2)
MM

@MM the OP อยากรู้ว่า0จะเหมือนกับ0.0
bolov

2
คำถามระบุว่า "คือ0 != 0.0อะไร" OP ไม่เคยถามว่า "เหมือนกัน" หรือไม่ สำหรับฉันแล้วดูเหมือนว่าเจตนาของคำถามคือจะทำอย่างไรกับd/0พฤติกรรมที่แตกต่างออกไปหรือไม่d/0.0
MM

2
@MM - สหกรณ์ไม่ถาม พวกเขาไม่ได้แสดง SO netiquette ที่เหมาะสมกับการแก้ไขค่าคงที่เหล่านั้น
StoryTeller - Unslander Monica

7

ฉันจะยืนยันว่าfoo/0และfoo/0.0มีไม่เหมือนกัน กล่าวคือผลที่ได้ของการหารครั้งแรก (การหารจำนวนเต็มหรือการหารทศนิยม) นั้นขึ้นอยู่กับประเภทของอย่างfooมากในขณะที่ผลที่สองไม่เป็นความจริง (จะเป็นการหารทศนิยมเสมอ)

ไม่ว่าสองข้อใดเป็น UB นั้นไม่เกี่ยวข้อง อ้างถึงมาตรฐาน:

พฤติกรรมที่ไม่ได้กำหนดที่อนุญาตมีตั้งแต่การเพิกเฉยต่อสถานการณ์โดยสิ้นเชิงด้วยผลลัพธ์ที่คาดเดาไม่ได้ไปจนถึงการทำงานระหว่างการแปลหรือการดำเนินการโปรแกรมในลักษณะที่เป็นเอกสารของสภาพแวดล้อม (มีหรือไม่มีการออกข้อความวินิจฉัย)ไปจนถึงการยุติการแปลหรือการดำเนินการ (ด้วยการออก ของข้อความวินิจฉัย)

(เน้นของฉัน)

พิจารณา " แนะนำวงเล็บรอบการกำหนดใช้เป็นค่าความจริง " คำเตือน: วิธีที่จะบอกคอมไพเลอร์ที่คุณจริงๆต้องการที่จะใช้ผลของการกำหนดคือโดยเป็นที่ชัดเจนและการเพิ่มวงเล็บรอบ ๆ ที่ได้รับมอบหมาย คำสั่งผลลัพธ์มีผลเหมือนกัน แต่จะบอกคอมไพเลอร์ว่าคุณรู้ว่าคุณกำลังทำอะไร อาจกล่าวได้เช่นเดียวกันfoo/0.0: เนื่องจากคุณกำลังบอกคอมไพเลอร์อย่างชัดเจนว่า "นี่คือการแบ่งจุดลอยตัว" โดยใช้0.0แทน0คอมไพเลอร์จึงเชื่อใจคุณและจะไม่ออกคำเตือน


1
ทั้งสองต้องผ่านการแปลงเลขคณิตปกติเพื่อให้เป็นประเภททั่วไปซึ่งจะทำให้ทั้งสองกรณีมีการแบ่งจุดลอยตัว
Shafik Yaghmour

@ShafikYaghmour คุณพลาดประเด็นในคำตอบ โปรดทราบว่าฉันไม่เคยพูดถึงประเภทของfooไฟล์. นั่นคือเจตนา คำยืนยันของคุณจะเป็นจริงในกรณีที่fooเป็นประเภทจุดลอยเท่านั้น
Cássio Renan

ฉันไม่ได้คอมไพเลอร์มีข้อมูลประเภทและเข้าใจการแปลงบางทีตัววิเคราะห์แบบคงที่ตามข้อความอาจถูกจับโดยสิ่งเหล่านี้ แต่คอมไพเลอร์ไม่ควร
Shafik Yaghmour

ประเด็นของฉันคือใช่คอมไพเลอร์รู้การแปลงเลขคณิตตามปกติ แต่มันเลือกที่จะไม่ออกคำเตือนเมื่อโปรแกรมเมอร์กำลังมีความชัดเจน ประเด็นทั้งหมดคือนี่อาจไม่ใช่ข้อบกพร่อง แต่เป็นพฤติกรรมโดยเจตนาแทน
Cássio Renan

จากนั้นเอกสารที่ฉันชี้ไปนั้นไม่ถูกต้องเนื่องจากทั้งสองกรณีเป็นการแบ่งทศนิยม ดังนั้นเอกสารไม่ถูกต้องหรือการวินิจฉัยมีข้อบกพร่อง
Shafik Yaghmour

4

สิ่งนี้ดูเหมือนข้อบกพร่องของ gcc เอกสารสำหรับ-Wno-div-by-zero ระบุไว้อย่างชัดเจนว่า :

อย่าเตือนเกี่ยวกับการหารจำนวนเต็มเวลาคอมไพล์ด้วยศูนย์ การแบ่งจุดลอยตัวด้วยศูนย์ไม่ได้รับการเตือนเนื่องจากอาจเป็นวิธีที่ถูกต้องในการได้รับ infinities และ NaNs

และหลังจากการแปลงเลขคณิตปกติที่ครอบคลุมใน[expr.arith.conv]ตัวถูกดำเนินการทั้งสองจะเป็นสองเท่า :

ตัวดำเนินการไบนารีจำนวนมากที่คาดว่าตัวถูกดำเนินการของประเภทเลขคณิตหรือการแจงนับทำให้เกิดการแปลงและประเภทผลลัพธ์ในลักษณะเดียวกัน จุดประสงค์คือเพื่อให้ได้ประเภททั่วไปซึ่งเป็นประเภทของผลลัพธ์ด้วย รูปแบบนี้เรียกว่าการแปลงเลขคณิตตามปกติซึ่งกำหนดไว้ดังนี้:

...

- มิฉะนั้นถ้าตัวถูกดำเนินการเป็นสองเท่าอีกตัวจะถูกแปลงเป็นสองเท่า

และ[expr.mul] :

ตัวถูกดำเนินการของ * และ / ต้องมีประเภทการแจงนับเลขคณิตหรือไม่กำหนดขอบเขต ตัวถูกดำเนินการของ% จะต้องมีประเภทการแจงนับหนึ่งหรือไม่ได้กำหนดขอบเขต การแปลงเลขคณิตตามปกติจะดำเนินการกับตัวถูกดำเนินการและกำหนดประเภทของผลลัพธ์

ด้วยความเคารพว่าจุดลอยหารด้วยศูนย์เป็นพฤติกรรมที่ไม่ได้กำหนดและวิธีการที่แตกต่างกันการดำเนินการจัดการกับมันดูเหมือนคำตอบของฉันที่นี่ TL; DR; ดูเหมือนว่า gcc จะเป็นไปตามภาคผนวก F wrt ที่จุดลอยตัวหารด้วยศูนย์ดังนั้น undefined จึงไม่มีบทบาทที่นี่ คำตอบจะแตกต่างกันสำหรับเสียงดัง


2

การหารจุดลอยตัวด้วยศูนย์จะทำงานแตกต่างจากการหารจำนวนเต็มด้วยศูนย์

IEEE floating point differentiates มาตรฐานระหว่าง INF + และ -inf ขณะจำนวนเต็มไม่สามารถเก็บอินฟินิตี้ การหารจำนวนเต็มโดยผลลัพธ์เป็นศูนย์คือพฤติกรรมที่ไม่ได้กำหนดไว้ การหารจุดลอยตัวด้วยศูนย์ถูกกำหนดโดยมาตรฐานจุดลอยตัวและผลลัพธ์เป็น + inf หรือ -inf


2
นี่คือความจริง แต่ก็ไม่ชัดเจนว่านี้มีความเกี่ยวข้องกับคำถามตั้งแต่ลอยส่วนจุดที่จะดำเนินการในทั้งสองกรณี ไม่มีการหารจำนวนเต็มในรหัสของ OP
Konrad Rudolph
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.