มันอันตรายแค่ไหนในการเปรียบเทียบค่าทศนิยม


391

ฉันรู้ว่าUIKitใช้CGFloatเพราะระบบพิกัดความละเอียดอิสระ

แต่ทุกครั้งที่ผมต้องการตรวจสอบเช่นถ้าframe.origin.xเป็น0มันทำให้ฉันรู้สึกป่วย

if (theView.frame.origin.x == 0) {
    // do important operation
}

ไม่ได้เป็นCGFloatความเสี่ยงที่จะบวกเท็จเมื่อเทียบกับ==, <=, >=, <, >? มันเป็นจุดลอยตัวและพวกเขามีปัญหาที่ไม่แม่นยำ0.0000000000041เช่น:

การObjective-Cจัดการภายในนี้เกิดขึ้นเมื่อทำการเปรียบเทียบหรือเกิดขึ้นที่สิ่งorigin.xที่อ่านเป็นศูนย์ไม่ได้เปรียบเทียบกับ0ความจริง

คำตอบ:


466

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

ก่อนอื่นถ้าคุณต้องการโปรแกรมที่มีทศนิยมคุณควรอ่านสิ่งนี้:

สิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ทุกคนควรรู้เกี่ยวกับเลขคณิตทศนิยม

ใช่อ่านทั้งหมด หากเป็นภาระมากเกินไปคุณควรใช้จำนวนเต็ม / จุดคงที่สำหรับการคำนวณของคุณจนกว่าคุณจะมีเวลาอ่าน :-)

ขณะนี้จากที่กล่าวมาปัญหาที่ใหญ่ที่สุดที่มีการเปรียบเทียบจุดลอยตัวที่แน่นอนนั้นมาลงที่:

  1. ความจริงที่ว่าจำนวนมากของค่าคุณอาจจะเขียนในแหล่งที่มาหรืออ่านด้วยscanfหรือstrtod, ไม่อยู่เป็นค่าจุดลอยและได้รับการแปลงเงียบไปประมาณที่ใกล้ที่สุด นี่คือสิ่งที่คำตอบของอสูร 9733 กำลังพูดถึง

  2. ความจริงที่ว่าผลลัพธ์จำนวนมากปัดเศษขึ้นเนื่องจากไม่มีความแม่นยำเพียงพอที่จะแสดงผลลัพธ์ที่แท้จริง ตัวอย่างง่ายๆที่คุณสามารถดูสิ่งนี้คือการเพิ่มx = 0x1fffffeและy = 1ลอย ที่นี่xมีความแม่นยำ 24 บิตใน mantissa (ok) และyมีเพียง 1 บิต แต่เมื่อคุณเพิ่มบิตของพวกเขาจะไม่อยู่ในที่ซ้อนกันและผลลัพธ์จะต้องมีความแม่นยำ 25 บิต แต่จะถูกปัดเศษ (แทน0x2000000ในโหมดปัดเศษเริ่มต้น)

  3. ความจริงที่ว่าผลลัพธ์จำนวนมากได้รับการปัดเศษเนื่องจากต้องการสถานที่มากมายสำหรับค่าที่ถูกต้อง ซึ่งรวมถึงผลลัพธ์ที่เป็นเหตุเป็นผลเช่น 1/3 (ซึ่งคุณคุ้นเคยจากทศนิยมที่ใช้หลายตำแหน่ง) แต่ก็ 1/10 ด้วย (ซึ่งใช้หลายสถานที่ในไบนารีด้วยอนันต์เนื่องจาก 5 ไม่ใช่พลังของ 2) เช่นเดียวกับผลลัพธ์ที่ไม่ลงตัวเช่นสแควร์รูทของสิ่งที่ไม่ใช่สแควร์ที่สมบูรณ์แบบ

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

  5. ฟังก์ชั่นยอดเยี่ยม ( trig, exp, logฯลฯ ) จะไม่ได้ระบุว่าจะมีผลโค้งมนอย่างถูกต้อง ผลลัพธ์ถูกระบุเพียงเพื่อให้ถูกต้องภายในหนึ่งหน่วยในตำแหน่งสุดท้ายของความแม่นยำ (มักเรียกว่า1ulp )

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

แก้ไข:โดยเฉพาะการตรวจสอบ epsilon ที่มีขนาดสัมพันธ์กันควรมีลักษณะดังนี้:

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))

ในกรณีที่FLT_EPSILONเป็นค่าคงที่จากfloat.h(แทนที่ด้วยDBL_EPSILONสำหรับdoubleหรือLDBL_EPSILONสำหรับlong doubles) และKเป็นค่าคงที่คุณเลือกเช่นว่าข้อผิดพลาดสะสมของการคำนวณของคุณเป็นที่สิ้นสุดแน่นอนโดยKหน่วยในสถานที่สุดท้าย (และถ้าคุณไม่แน่ใจว่าคุณได้รับข้อผิดพลาด การคำนวณที่ถูกผูกไว้ถูกต้องทำให้Kใหญ่กว่าที่การคำนวณของคุณควรจะเป็นสองเท่า

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

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)

และทดแทนเช่นเดียวกันDBL_MINหากใช้คู่ผสม


25
fabs(x+y)เป็นปัญหาถ้าxและy(สามารถ) มีเครื่องหมายต่างกัน ยังคงเป็นคำตอบที่ดีในการเปรียบเทียบการเปรียบเทียบสินค้ากับลัทธิ
Daniel Fischer

27
หากxและyมีสัญญาณที่แตกต่างกันก็ไม่มีปัญหา ด้านขวามือจะเป็น "เล็กเกินไป" แต่เนื่องจากxและyมีเครื่องหมายต่างกันพวกเขาไม่ควรเปรียบเทียบเท่ากัน (ยกเว้นกรณีที่มีขนาดเล็กจนผิดปกติ แต่ในกรณีที่สองจับได้)
R. GitHub หยุดช่วยเหลือน้ำแข็ง

4
ฉันอยากรู้เกี่ยวกับคำแถลงของคุณ: "โดยเฉพาะอย่างยิ่งสำหรับคอมไพเลอร์ buggy และไม่สอดคล้องเช่น GCC" GCC เป็นรถบั๊กกี้จริงหรือไม่เป็นไปตามมาตรฐานหรือไม่?
Nicolás Ozimica

3
เนื่องจากคำถามถูกติดแท็ก iOS เป็นที่น่าสังเกตว่าคอมไพเลอร์ของ Apple (ทั้งเสียงดังกราวและการสร้าง gcc ของ Apple) ได้ใช้ FLT_EVAL_METHOD = 0 เสมอและพยายามเข้มงวดอย่างเต็มที่เกี่ยวกับการไม่ให้ความแม่นยำสูงเกินไป หากคุณพบการละเมิดใด ๆ โปรดรายงานข้อผิดพลาด
Stephen Canon

17
"ประการแรกค่าจุดลอยตัวไม่ได้" สุ่ม "ในพฤติกรรมของพวกเขาการเปรียบเทียบที่แน่นอนสามารถและทำให้เข้าใจในการใช้งานจริงมากมาย" - เพียงสองประโยคและได้รับ +1 แล้ว! นั่นเป็นหนึ่งในสิ่งที่ผู้คนเข้าใจผิดว่าเป็นของปลอมเมื่อทำงานกับจุดลอย
Christian Rau

36

ตั้งแต่ 0 นั้นสามารถแทนได้อย่างแน่นอนว่าเป็นเลขทศนิยมของ IEEE754 (หรือการใช้ตัวเลข fp อื่น ๆ ที่ฉันเคยทำงานด้วย) เปรียบเทียบกับ 0 จึงน่าจะปลอดภัย คุณอาจถูกกัดได้อย่างไรก็ตามหากโปรแกรมของคุณคำนวณค่า (เช่นtheView.frame.origin.x) ซึ่งคุณมีเหตุผลที่เชื่อว่าควรเป็น 0 แต่การคำนวณของคุณไม่สามารถรับประกันว่าจะเป็น 0

เพื่อชี้แจงเล็กน้อยการคำนวณเช่น:

areal = 0.0

จะ (ยกเว้นภาษาหรือระบบของคุณเสีย) สร้างค่าเช่นนั้น (areal == 0.0) จะส่งกลับค่าจริง แต่การคำนวณอื่นเช่น

areal = 1.386 - 2.1*(0.66)

อาจจะไม่.

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

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

สำหรับ Angry Birds นั้นไม่อันตรายเลย


11
ที่จริงแล้ว1.30 - 2*(0.65)เป็นตัวอย่างที่สมบูรณ์แบบของการแสดงออกที่เห็นได้ชัดว่ามีค่าเป็น 0.0 หากคอมไพเลอร์ของคุณใช้ IEEE 754 เพราะคู่ที่แสดงเป็น0.65และ1.30มีนัยสำคัญเท่ากันและการคูณสองอย่างชัดเจน
ปาสกาล Cuoq

7
ยังคงได้รับตัวแทนจากอันนี้ดังนั้นฉันจึงเปลี่ยนตัวอย่างข้อมูลที่สอง
เครื่องหมายประสิทธิภาพสูง

22

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

จุดลอยในกราฟิกเป็นเรื่องปกติ! แต่ไม่จำเป็นต้องเปรียบเทียบทุ่นลอยโดยตรงเลย ทำไมคุณต้องทำเช่นนั้น? กราฟิกใช้ลอยเพื่อกำหนดช่วงเวลา และการเปรียบเทียบว่าการลอยตัวนั้นอยู่ในช่วงเวลาที่กำหนดโดยการลอยตัวนั้นถูกกำหนดไว้อย่างดีเสมอและจำเป็นต้องมีความสอดคล้องไม่แม่นยำหรือแม่นยำ! ตราบใดที่พิกเซล (ซึ่งก็เป็นช่วงเวลา!) สามารถกำหนดได้ว่ากราฟิกทั้งหมดนั้นต้องการ

ดังนั้นหากคุณต้องการทดสอบว่าจุดของคุณอยู่นอกช่วง [0..width [range หรือไม่นี่เป็นเรื่องปกติ เพียงให้แน่ใจว่าคุณกำหนดรวมอย่างสม่ำเสมอ ตัวอย่างเช่นนิยามข้างในเสมอคือ (x> = 0 && x <width) เช่นเดียวกันสำหรับการทดสอบทางแยกหรือการตี

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


13

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

เมื่อพูดถึงค่าที่สามารถแทนได้อย่างสมบูรณ์แบบคุณจะได้รับช่วง 24 บิตในแนวคิดเรื่องพลังของสอง (ความแม่นยำเดียว) ดังนั้น 1, 2, 4 จึงสามารถแทนได้อย่างสมบูรณ์แบบเช่น. 5, .25, และ. 125 ตราบใดที่บิตที่สำคัญทั้งหมดของคุณอยู่ใน 24 บิตคุณก็จะได้ทอง ดังนั้น 10.625 สามารถย้ำได้อย่างแม่นยำ

นี่ยอดเยี่ยม แต่จะกระจุยอย่างรวดเร็วภายใต้แรงกดดัน สถานการณ์สมมติสองประการที่ควรคำนึงถึง: 1) เมื่อมีการคำนวณที่เกี่ยวข้อง อย่าเชื่อว่า sqrt (3) * sqrt (3) == 3 มันจะไม่เป็นอย่างนั้น และมันอาจจะไม่อยู่ใน epsilon ตามที่คำตอบอื่น ๆ แนะนำ 2) เมื่อไม่มีส่วนร่วมที่ไม่ใช่พลังงานของ 2 (NPOT) ดังนั้นมันอาจฟังดูแปลก แต่ 0.1 เป็นอนุกรมที่ไม่มีที่สิ้นสุดในไบนารีดังนั้นการคำนวณใด ๆ ที่เกี่ยวข้องกับจำนวนเช่นนี้จะไม่ถูกต้องตั้งแต่เริ่มต้น

(โอ้และคำถามเดิมที่กล่าวถึงการเปรียบเทียบเป็นศูนย์อย่าลืมว่า -0.0 เป็นค่าทศนิยมที่ถูกต้องเช่นกัน)


11

[ว่า 'คำตอบที่ถูก' Kคัดสรรเลือก การเลือกที่Kจะเป็นแบบเฉพาะกิจเหมือนกับการเลือกVISIBLE_SHIFTแต่การเลือกKนั้นชัดเจนน้อยกว่าเพราะVISIBLE_SHIFTมันไม่ได้ต่ออยู่กับคุณสมบัติการแสดงผลใด ๆ ดังนั้นรับพิษของคุณ - เลือกหรือเลือกK VISIBLE_SHIFTคำตอบนี้สนับสนุนการเลือกVISIBLE_SHIFTแล้วแสดงให้เห็นถึงความยากลำบากในการเลือกK]

แม่นยำเนื่องจากข้อผิดพลาดแบบกลมคุณไม่ควรใช้การเปรียบเทียบค่า 'แน่นอน' สำหรับการดำเนินการทางตรรกะ ในกรณีเฉพาะของคุณตำแหน่งบนจอแสดงผลภาพมันอาจไม่สำคัญว่าตำแหน่งนั้นคือ 0.0 หรือ 0.0000000003 - ความแตกต่างนั้นมองไม่เห็นด้วยตา ดังนั้นตรรกะของคุณควรเป็นดังนี้:

#define VISIBLE_SHIFT    0.0001        // for example
if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }

อย่างไรก็ตามในท้ายที่สุด 'มองไม่เห็นด้วยตา' จะขึ้นอยู่กับคุณสมบัติการแสดงผลของคุณ หากคุณสามารถผูกการแสดงผลบน (คุณควรจะสามารถ); จากนั้นเลือกVISIBLE_SHIFTเป็นเศษส่วนของขอบเขตบนนั้น

ทีนี้คำตอบที่ถูกต้องKจะKมาดูกันดีกว่า 'คำตอบที่ถูกต้อง' ด้านบนพูดว่า:

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

Kดังนั้นเราจึงจำเป็น หากการเดินทางKยากขึ้นใช้งานง่ายกว่าการเลือกฉันVISIBLE_SHIFTคุณก็จะเป็นคนตัดสินใจว่าอะไรที่เหมาะกับคุณ เพื่อค้นหาว่าKเรากำลังจะเขียนโปรแกรมทดสอบที่ดูKค่ามากมายเพื่อให้เราสามารถดูว่ามันทำงานอย่างไร ควรรู้วิธีเลือกKอย่างไรถ้า 'คำตอบที่ถูกต้อง' สามารถใช้งานได้ ไม่มี?

เราจะใช้เป็นรายละเอียด 'คำตอบที่ถูกต้อง':

if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)

ลองทำตามค่าทั้งหมดของ K:

#include <math.h>
#include <float.h>
#include <stdio.h>

void main (void)
{
  double x = 1e-13;
  double y = 0.0;

  double K = 1e22;
  int i = 0;

  for (; i < 32; i++, K = K/10.0)
    {
      printf ("K:%40.16lf -> ", K);

      if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
        printf ("YES\n");
      else
        printf ("NO\n");
    }
}
ebg@ebg$ gcc -o test test.c
ebg@ebg$ ./test
K:10000000000000000000000.0000000000000000 -> YES
K: 1000000000000000000000.0000000000000000 -> YES
K:  100000000000000000000.0000000000000000 -> YES
K:   10000000000000000000.0000000000000000 -> YES
K:    1000000000000000000.0000000000000000 -> YES
K:     100000000000000000.0000000000000000 -> YES
K:      10000000000000000.0000000000000000 -> YES
K:       1000000000000000.0000000000000000 -> NO
K:        100000000000000.0000000000000000 -> NO
K:         10000000000000.0000000000000000 -> NO
K:          1000000000000.0000000000000000 -> NO
K:           100000000000.0000000000000000 -> NO
K:            10000000000.0000000000000000 -> NO
K:             1000000000.0000000000000000 -> NO
K:              100000000.0000000000000000 -> NO
K:               10000000.0000000000000000 -> NO
K:                1000000.0000000000000000 -> NO
K:                 100000.0000000000000000 -> NO
K:                  10000.0000000000000000 -> NO
K:                   1000.0000000000000000 -> NO
K:                    100.0000000000000000 -> NO
K:                     10.0000000000000000 -> NO
K:                      1.0000000000000000 -> NO
K:                      0.1000000000000000 -> NO
K:                      0.0100000000000000 -> NO
K:                      0.0010000000000000 -> NO
K:                      0.0001000000000000 -> NO
K:                      0.0000100000000000 -> NO
K:                      0.0000010000000000 -> NO
K:                      0.0000001000000000 -> NO
K:                      0.0000000100000000 -> NO
K:                      0.0000000010000000 -> NO

อาดังนั้น K ควรจะเป็น 1e16 หรือใหญ่กว่าถ้าฉันต้องการ 1e-13 ให้เป็น 'ศูนย์'

ดังนั้นฉันจะบอกว่าคุณมีสองตัวเลือก:

  1. ทำการคำนวณ epsilon อย่างง่าย ๆ โดยใช้วิจารณญาณทางวิศวกรรมของคุณสำหรับคุณค่าของ 'epsilon' ตามที่ฉันแนะนำ หากคุณกำลังทำกราฟิกและ 'ศูนย์' หมายถึง 'การเปลี่ยนแปลงที่มองเห็นได้' มากกว่าการตรวจสอบสินทรัพย์ภาพของคุณ (ภาพ ฯลฯ ) และตัดสินว่า epsilon สามารถเป็นอะไรได้
  2. อย่าพยายามคำนวณจุดใดลอยจนกว่าคุณจะได้อ่านอ้างอิงคำตอบที่ไม่ได้บรรทุกสินค้าศาสนา (และอากาศเอกของคุณในกระบวนการ) Kและจากนั้นใช้วิจารณญาณไม่ใช่งานง่ายของคุณเพื่อเลือก

10
แง่มุมหนึ่งของความเป็นอิสระในการแก้ปัญหาคือคุณไม่สามารถบอกได้ว่า "การเปลี่ยนแปลงที่มองเห็น" คืออะไรในเวลารวบรวม สิ่งที่มองไม่เห็นบนหน้าจอ super-HD อาจจะเห็นได้ชัดบนหน้าจอเล็ก ๆ อย่างน้อยหนึ่งควรทำให้มันเป็นฟังก์ชั่นของขนาดหน้าจอ หรือตั้งชื่ออย่างอื่น
Romain

1
แต่อย่างน้อยการเลือก 'shift shift ที่มองเห็นได้' นั้นขึ้นอยู่กับคุณสมบัติการแสดงผล (หรือเฟรม) ที่เข้าใจง่าย - ซึ่งแตกต่างจาก <คำตอบที่ถูกต้อง> Kซึ่งยากและไม่ง่ายต่อการเลือก
GoZoner

5

คำถามที่ถูกต้อง: ใครเปรียบเทียบคะแนนใน Cocoa Touch ได้อย่างไร

คำตอบที่ถูกต้อง: CGPointEqualToPoint ()

คำถามที่แตกต่าง: ค่าที่คำนวณสองค่าเหมือนกันหรือไม่

คำตอบที่โพสต์ที่นี่: พวกเขาไม่ได้

จะตรวจสอบว่าพวกเขาอยู่ใกล้ได้อย่างไร? หากคุณต้องการตรวจสอบว่าพวกเขาปิดแล้วอย่าใช้ CGPointEqualToPoint () แต่อย่าตรวจสอบเพื่อดูว่าพวกเขาอยู่ใกล้ ทำสิ่งที่สมเหตุสมผลในโลกแห่งความเป็นจริงเช่นการตรวจสอบเพื่อดูว่าจุดใดที่อยู่นอกเส้นหรือถ้าจุดนั้นอยู่ในทรงกลม


4

ครั้งสุดท้ายที่ฉันตรวจสอบมาตรฐาน C ไม่มีข้อกำหนดสำหรับการดำเนินการกับจำนวนจุดลอยตัว (รวม 64 บิต, 53 บิตแมนทิสสา) เพื่อความแม่นยำมากกว่าความแม่นยำนั้น อย่างไรก็ตามฮาร์ดแวร์บางตัวอาจทำการดำเนินการในการลงทะเบียนที่มีความแม่นยำมากขึ้นและข้อกำหนดถูกตีความว่าไม่จำเป็นต้องมีการล้างบิตลำดับที่ต่ำกว่า ดังนั้นคุณจะได้รับผลลัพธ์ที่ไม่คาดคิดจากการเปรียบเทียบเช่นนี้ขึ้นอยู่กับสิ่งที่เหลืออยู่ในทะเบียนจากใครก็ตามที่เคยนอนที่นั่น

ที่กล่าวไว้และแม้ว่าฉันจะพยายามที่จะลบล้างมันเมื่อใดก็ตามที่ฉันเห็นชุดที่ฉันทำงานมีรหัส C จำนวนมากที่รวบรวมโดยใช้ gcc และทำงานบน linux และเราไม่ได้สังเกตผลลัพธ์ที่ไม่คาดคิดเหล่านี้ในเวลานานมาก . ฉันไม่รู้ว่านี่เป็นเพราะ gcc กำลังเคลียร์บิตลำดับต่ำสำหรับเราการลงทะเบียน 80 บิตไม่ได้ใช้สำหรับการดำเนินการเหล่านี้กับคอมพิวเตอร์ที่ทันสมัยมาตรฐานเปลี่ยนไปหรืออะไร ฉันต้องการที่จะรู้ว่าใครสามารถอ้างอิงบทและข้อ


1

คุณสามารถใช้รหัสดังกล่าวเพื่อเปรียบเทียบ float กับศูนย์:

if ((int)(theView.frame.origin.x * 100) == 0) {
    // do important operation
}

สิ่งนี้จะเปรียบเทียบกับความแม่นยำ 0.1 ซึ่งเพียงพอสำหรับ CGFloat ในกรณีนี้


หล่อไปintได้โดยไม่ต้องประกันภัยtheView.frame.origin.xอยู่ใน / ใกล้ช่วงของการที่intจะนำไปสู่พฤติกรรมที่ไม่ได้กำหนด (UB) - หรือในกรณีนี้, 1 / 100th intช่วงของ
chux - Reinstate Monica

ไม่มีเหตุผลใดที่จะแปลงเป็นจำนวนเต็มเช่นนี้ ดังที่ chux กล่าวว่ามีศักยภาพสำหรับ UB จากค่านอกขอบเขต และในบางสถาปัตยกรรมสิ่งนี้จะช้ากว่าการคำนวณในจุดลอย สุดท้ายคูณด้วย 100 ไลค์ที่จะเปรียบเทียบกับความแม่นยำ 0.01 ไม่ใช่ 0.1
Sneftel

0
-(BOOL)isFloatEqual:(CGFloat)firstValue secondValue:(CGFloat)secondValue{

BOOL isEqual = NO;

NSNumber *firstValueNumber = [NSNumber numberWithDouble:firstValue];
NSNumber *secondValueNumber = [NSNumber numberWithDouble:secondValue];

isEqual = [firstValueNumber isEqualToNumber:secondValueNumber];

return isEqual;

}


0

ฉันใช้ฟังก์ชันการเปรียบเทียบต่อไปนี้เพื่อเปรียบเทียบจำนวนทศนิยม:

bool compare(const double value1, const double value2, const int precision)
{
    int64_t magnitude = static_cast<int64_t>(std::pow(10, precision));
    int64_t intValue1 = static_cast<int64_t>(value1 * magnitude);
    int64_t intValue2 = static_cast<int64_t>(value2 * magnitude);
    return intValue1 == intValue2;
}

// Compare 9 decimal places:
if (compare(theView.frame.origin.x, 0, 9)) {
    // do important operation
}

-6

ฉันจะบอกว่าสิ่งที่ถูกต้องคือการประกาศแต่ละหมายเลขเป็นวัตถุแล้วกำหนดสามสิ่งในวัตถุนั้น: 1) ตัวดำเนินการความเสมอภาค 2) วิธี setAcceptableDifference 3) ค่าตัวมันเอง ตัวดำเนินการความเท่าเทียมจะส่งกลับค่าจริงถ้าผลต่างที่แท้จริงของสองค่านั้นน้อยกว่าค่าที่ตั้งไว้เป็นที่ยอมรับ

คุณสามารถคลาสย่อยวัตถุเพื่อให้เหมาะกับปัญหา ตัวอย่างเช่นเหล็กเส้นกลมของโลหะที่มีขนาดตั้งแต่ 1 ถึง 2 นิ้วอาจถูกพิจารณาว่ามีขนาดเส้นผ่าศูนย์กลางเท่ากันถ้าเส้นผ่านศูนย์กลางต่างกันน้อยกว่า 0.0001 นิ้ว ดังนั้นคุณจะเรียกใช้ setAcceptableDifference ด้วยพารามิเตอร์ 0.0001 จากนั้นใช้โอเปอเรเตอร์ความเท่าเทียมกันด้วยความมั่นใจ


1
นี่ไม่ใช่คำตอบที่ดี ก่อนสิ่ง "วัตถุสิ่งของ" ทั้งหมดไม่ได้ทำสิ่งใดเพื่อแก้ไขปัญหาของคุณ และประการที่สองการใช้งาน "ความเท่าเทียมกัน" ที่แท้จริงของคุณนั้นไม่ใช่สิ่งที่ถูกต้อง
Tom Swirly

3
ทอมบางทีคุณอาจคิดอีกครั้งเกี่ยวกับ "สิ่งที่วัตถุ" ด้วยจำนวนจริงที่แสดงถึงความแม่นยำสูงความเท่าเทียมกันจึงเกิดขึ้นน้อยมาก แต่ความคิดของคนในเรื่องความเสมอภาคนั้นอาจปรับให้เหมาะกับคุณ มันจะดีกว่าหากมีโอเปอเรเตอร์ 'ประมาณเท่ากัน' overridable แต่ไม่มี
John White
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.