การตรวจสอบค่าทศนิยมเพื่อความเท่าเทียมกันเป็น 0 ปลอดภัยหรือไม่


100

ฉันรู้ว่าคุณไม่สามารถพึ่งพาความเท่าเทียมกันระหว่างค่าประเภทสองหรือทศนิยมได้ตามปกติ แต่ฉันสงสัยว่า 0 เป็นกรณีพิเศษหรือไม่

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

แต่ฉันไม่รู้เกี่ยวกับหัวข้อนี้มากนักจึงไม่เหมาะที่จะพูด

double x = 0.0;
return (x == 0.0) ? true : false;

จะกลับมาจริงหรือไม่?


69
ตัวดำเนินการ ternary ซ้ำซ้อนในรหัสนั้น :)
Joel Coehoorn

5
ฮ่า ๆ คุณพูดถูก Go me
Gene Roberts

ฉันจะไม่ทำเพราะคุณไม่รู้ว่า x ตั้งค่าเป็นศูนย์ได้อย่างไร หากคุณยังต้องการทำคุณอาจต้องการปัดเศษหรือปูพื้น x เพื่อกำจัด 1e-12 หรือที่อาจติดแท็กในตอนท้าย
Rex Logan

คำตอบ:


115

มันเป็นความปลอดภัยที่จะคาดหวังว่าการเปรียบเทียบจะกลับมาtrueและถ้าหากตัวแปรคู่มีค่าตรง0.0(ซึ่งในตัวอย่างรหัสต้นฉบับของคุณเป็นของแน่นอนกรณี) สิ่งนี้สอดคล้องกับความหมายของตัว==ดำเนินการ a == bหมายถึง " aเท่ากับb"

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


51

หากคุณจำเป็นต้องทำการเปรียบเทียบ "ความเท่าเทียมกัน" จำนวนมากคุณควรเขียนฟังก์ชันตัวช่วยเล็ก ๆ น้อย ๆ หรือวิธีการขยายใน. NET 3.5 เพื่อเปรียบเทียบ:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

สามารถใช้วิธีต่อไปนี้:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);

4
คุณอาจมีข้อผิดพลาดในการยกเลิกแบบลบโดยการเปรียบเทียบ double1 และ double2 ในกรณีที่ตัวเลขเหล่านี้มีค่าใกล้กันมาก ฉันจะลบ Math.Abs ​​และตรวจสอบแต่ละสาขาทีละสาขา d1> = d2 - e และ d1 <= d2 + e
Theodore Zographos

"เนื่องจาก Epsilon กำหนดนิพจน์ต่ำสุดของค่าบวกซึ่งมีช่วงอยู่ใกล้ศูนย์ระยะขอบของความแตกต่างระหว่างค่าที่คล้ายกันสองค่าจะต้องมากกว่า Epsilon โดยปกติแล้วจะมากกว่า Epsilon หลายเท่าด้วยเหตุนี้เราจึงแนะนำให้คุณทำ อย่าใช้ Epsilon เมื่อเปรียบเทียบค่า Double เพื่อความเท่าเทียมกัน " - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
Rafael Costa

15

สำหรับตัวอย่างง่ายๆของคุณการทดสอบนั้นใช้ได้ แต่สิ่งนี้:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

โปรดจำไว้ว่า. 1 เป็นทศนิยมซ้ำในไบนารีและไม่สามารถแทนค่าได้อย่างแน่นอน จากนั้นเปรียบเทียบกับรหัสนี้:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

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


5
อันที่จริงสิ่งนี้ส่งคืนจริงด้วยเหตุผลบางประการ (อย่างน้อยใน LINQPad)
Alexey Romanov

".1 issue" ที่คุณพูดถึงคืออะไร
Teejay

14

จากรายการ MSDN สำหรับDouble.Equals :

ความแม่นยำในการเปรียบเทียบ

ควรใช้เมธอด Equals ด้วยความระมัดระวังเนื่องจากค่าที่เท่ากันสองค่าอาจไม่เท่ากันเนื่องจากความแม่นยำที่แตกต่างกันของทั้งสองค่า ตัวอย่างต่อไปนี้รายงานว่าค่า Double .3333 และค่า Double ที่ส่งกลับโดยการหาร 1 ด้วย 3 นั้นไม่เท่ากัน

...

แทนที่จะเปรียบเทียบเพื่อความเท่าเทียมเทคนิคหนึ่งที่แนะนำเกี่ยวข้องกับการกำหนดส่วนต่างของค่าที่ยอมรับได้ระหว่างค่าสองค่า (เช่น. 01% ของค่าใดค่าหนึ่ง) หากค่าสัมบูรณ์ของความแตกต่างระหว่างค่าทั้งสองน้อยกว่าหรือเท่ากับระยะขอบนั้นความแตกต่างน่าจะเกิดจากความแตกต่างในด้านความแม่นยำดังนั้นค่าจึงน่าจะเท่ากัน ตัวอย่างต่อไปนี้ใช้เทคนิคนี้เพื่อเปรียบเทียบ. 33333 และ 1/3 ค่า Double สองค่าที่ตัวอย่างโค้ดก่อนหน้าพบว่าไม่เท่ากัน

ยังดูDouble.Epsilon


1
นอกจากนี้ยังสามารถเปรียบเทียบค่าที่ไม่เทียบเท่ากันได้ ใครจะคาดคิดว่าถ้าx.Equals(y)ไปแล้ว(1/x).Equals(1/y)แต่นั่นไม่ใช่กรณีที่xเป็น0และเป็นy 1/Double.NegativeInfinityค่าเหล่านี้ประกาศว่าเท่าเทียมกันแม้ว่าต่างตอบแทนจะไม่
supercat

@supercat: เทียบเท่ากัน และพวกเขาไม่มีซึ่งกันและกัน คุณสามารถเรียกใช้การทดสอบอีกครั้งด้วยx = 0และและคุณยังคงพบว่าy = 0 1/x != 1/y
Ben Voigt

@ BenVoigt: มีxและyเป็นประเภทdouble? คุณเปรียบเทียบผลลัพธ์อย่างไรเพื่อให้รายงานไม่เท่ากัน โปรดทราบว่า 1 / 0.0 ไม่ใช่ NaN
supercat

@supercat: โอเคเป็นหนึ่งในสิ่งที่ IEEE-754 ผิดพลาด (ประการแรกที่1.0/0.0ล้มเหลวในการเป็น NaN อย่างที่ควรจะเป็นเนื่องจากขีด จำกัด ไม่ซ้ำกันประการที่สอง infinities นั้นเปรียบเทียบกันโดยไม่สนใจองศาของอินฟินิตี้)
Ben Voigt

@BenVoigt: ถ้าศูนย์เป็นผลมาจากการคูณสองจำนวนที่มีขนาดเล็กมากจากนั้นหาร 1.0 ลงในนั้นควรให้ค่าที่เปรียบเทียบได้มากกว่าจำนวนใด ๆ ของตัวเลขขนาดเล็กที่มีเครื่องหมายเดียวกันและน้อยกว่าจำนวนใด ๆ หากหนึ่งในจำนวนน้อย ตัวเลขมีเครื่องหมายตรงกันข้าม IMHO, IEEE-754 จะดีกว่าถ้ามีศูนย์ที่ไม่ได้ลงนาม แต่มีจำนวนน้อยในเชิงบวกและเชิงลบ
supercat

6

ปัญหาเกิดขึ้นเมื่อคุณเปรียบเทียบการใช้ค่าทศนิยมประเภทต่างๆเช่นการเปรียบเทียบ float กับ double แต่ถ้าเป็นประเภทเดียวกันก็ไม่น่ามีปัญหา

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

ปัญหาคือบางครั้งโปรแกรมเมอร์ลืมไปว่าการร่ายประเภทโดยนัย (double to float) กำลังเกิดขึ้นสำหรับการเปรียบเทียบและส่งผลให้เกิดข้อผิดพลาด


3

หากตัวเลขถูกกำหนดโดยตรงให้กับโฟลต์หรือสองเท่าก็ปลอดภัยที่จะทดสอบกับศูนย์หรือจำนวนเต็มใด ๆ ที่สามารถแสดงเป็น 53 บิตสำหรับสองเท่าหรือ 24 บิตสำหรับโฟลต

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

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


ฉันเห็นด้วยกับคำชี้แจงของคุณโดยทั่วไป (และเพิ่มคะแนน) แต่ฉันเชื่อว่ามันขึ้นอยู่กับว่ามีการใช้จุดลอยตัว IEEE 754 หรือไม่ และฉันเชื่อว่าคอมพิวเตอร์ที่ "ทันสมัย" ทุกเครื่องใช้ IEEE 754 เป็นอย่างน้อยก็เพื่อการจัดเก็บลอย
Mark Lakata

2

ไม่มันไม่ตกลง ที่เรียกว่าค่า denormalized (ค่าปกติ) เมื่อเทียบกับ 0.0 จะเปรียบเทียบเป็นเท็จ (ไม่ใช่ศูนย์) แต่เมื่อใช้ในสมการจะถูกทำให้เป็นมาตรฐาน (กลายเป็น 0.0) ดังนั้นการใช้สิ่งนี้เป็นกลไกเพื่อหลีกเลี่ยงการหารด้วยศูนย์จึงไม่ปลอดภัย ให้เพิ่ม 1.0 และเปรียบเทียบกับ 1.0 แทน สิ่งนี้จะช่วยให้มั่นใจได้ว่า subnormals ทั้งหมดถือเป็นศูนย์


Subnormals เรียกอีกอย่างว่าdenormals
Manuel

Subnormals จะไม่เท่ากับศูนย์เมื่อใช้แม้ว่าอาจหรือไม่ให้ผลลัพธ์เดียวกันขึ้นอยู่กับการทำงานที่แน่นอน
wnoise


-4

อันที่จริงฉันคิดว่าควรใช้รหัสต่อไปนี้เพื่อเปรียบเทียบค่าสองเท่ากับ 0.0:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

เหมือนกันสำหรับลอย:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;

5
ไม่ได้จากเอกสารจาก double.Epsilon: "หากคุณสร้างอัลกอริทึมแบบกำหนดเองที่กำหนดว่าตัวเลขทศนิยมสองตำแหน่งสามารถถือว่าเท่ากันได้หรือไม่คุณต้องใช้ค่าที่มากกว่าค่าคงที่ของ Epsilon เพื่อสร้างความแตกต่างสัมบูรณ์ที่ยอมรับได้ เพื่อให้ค่าทั้งสองถือว่าเท่ากัน (โดยปกติขอบของความแตกต่างนั้นมากกว่า Epsilon หลายเท่า) "
Alastair Maw

1
@AlastairMaw สิ่งนี้ใช้กับการตรวจสอบสองเท่าของขนาดใด ๆ เพื่อความเท่าเทียมกัน สำหรับการตรวจสอบความเท่าเทียมกันเป็นศูนย์ให้ double Epsilon ใช้ได้
jwg

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

4
ตัวอย่างเช่น: (1.0 / 5.0 + 1.0 / 5.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0) <double.Epsilon == false (และอย่างมากในแง่ขนาด: 2.78E-17 เทียบกับ 4.94E -324)
Alastair Maw

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