เป็นไปได้ไหมที่จะได้ 0 โดยการลบเลขทศนิยมสองตัวที่ไม่เท่ากัน?


131

เป็นไปได้ไหมที่จะหารด้วย 0 (หรืออินฟินิตี้) ในตัวอย่างต่อไปนี้

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

ในกรณีปกติจะไม่แน่นอน แต่สิ่งที่ถ้าaและbมีความใกล้ชิดสามารถ(a-b)ส่งผลในการเป็น0เนื่องจากความแม่นยำของการคำนวณ?

โปรดทราบว่าคำถามนี้ใช้สำหรับ Java แต่ฉันคิดว่ามันจะใช้ได้กับภาษาโปรแกรมส่วนใหญ่


49
ฉันจะต้องลองใช้คู่ผสมทั้งหมดซึ่งจะใช้เวลาสักครู่ :)
Thirler

3
@ Thirler ดูเหมือนจะเป็นเวลาที่จะใช้ JUnit Testing กับฉัน!
Matt Clark

7
@bluebrain ฉันเดาว่าเลข 2.000 ฯลฯ ของคุณมีทศนิยมจำนวนมากที่จะแสดงด้วยการลอย ดังนั้นสุดท้ายจะไม่แสดงด้วยจำนวนที่ใช้จริงในการเปรียบเทียบ
Thirler

4
@ Thirler คง. 'คุณไม่สามารถรับประกันได้ว่าหมายเลขที่คุณกำหนดให้กับโฟลตหรือสองเท่านั้นแน่นอน'
guness

4
โปรดทราบว่าการคืนค่า 0 ในกรณีนั้นอาจนำไปสู่ความคลุมเครือที่ยากต่อการดีบั๊กดังนั้นตรวจสอบให้แน่ใจว่าคุณต้องการคืนค่า 0 แทนที่จะส่งข้อยกเว้นหรือส่งคืน NaN
m0skit0

คำตอบ:


132

ใน Java a - bจะไม่เท่ากับถ้า0 a != bเนื่องจาก Java กำหนดให้การดำเนินการจุดลอยตัวของ IEEE 754 ซึ่งรองรับตัวเลขที่ถูกทำให้ผิดปกติ จากข้อมูลจำเพาะ :

โดยเฉพาะอย่างยิ่งภาษาการเขียนโปรแกรม Java ต้องการการสนับสนุนของ IEEE 754 floating-point numbers และ underflow ทีละน้อยซึ่งทำให้ง่ายต่อการพิสูจน์คุณสมบัติที่ต้องการของอัลกอริทึมเชิงตัวเลขโดยเฉพาะ การดำเนินการจุดลอยตัวจะไม่ "ฟลัชเป็นศูนย์" หากผลลัพธ์ที่คำนวณได้เป็นตัวเลขที่ถูกทำให้ผิดปกติ

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

สำหรับภาษาอื่น ๆ นั้นขึ้นอยู่กับ ใน C หรือ C ++ การสนับสนุน IEEE 754 เป็นทางเลือก

ที่กล่าวว่ามันเป็นไปได้สำหรับการแสดงออก2 / (a - b)ที่จะล้นเช่นกับและa = 5e-308b = 4e-308


4
อย่างไรก็ตาม OP ต้องการทราบเกี่ยวกับ 2 / (ab) แบบนี้รับรองฟินแน่นอน?
Taemyr

ขอบคุณสำหรับคำตอบฉันได้เพิ่มลิงก์ไปยังวิกิพีเดียสำหรับคำอธิบายเกี่ยวกับตัวเลขที่ผิดปกติ
Thirler

3
@Taemyr ดูการแก้ไขของฉัน กองจริงสามารถล้น
nwellnhof

@Taemyr (a,b) = (3,1)=> 2/(a-b) = 2/(3-1) = 2/2 = 1ว่านี่เป็นความจริงกับจุดลอยตัวของ IEEE หรือไม่ฉันไม่รู้
Cole Johnson

1
@DrewDormann IEEE 754 เป็นทางเลือกสำหรับ C99 ดูภาคผนวก F ของมาตรฐาน
nwellnhof

50

วิธีแก้ปัญหาต่อไปนี้มีอะไรบ้าง?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

วิธีนี้จะทำให้คุณไม่ต้องพึ่งพาการรองรับ IEEE ในภาษาใด ๆ


6
หลีกเลี่ยงปัญหาและลดความซับซ้อนของการทดสอบในครั้งเดียว ฉันชอบ.
Joshua

11
-1 ถ้าคุณไม่ควรที่จะกลับมาa=b 0การหารด้วย0IEEE 754 ทำให้คุณไม่มีที่สิ้นสุดไม่ใช่ข้อยกเว้น คุณกำลังหลีกเลี่ยงปัญหาดังนั้นการกลับมาจึง0เป็นปัญหาที่รอให้เกิดขึ้น พิจารณา1/x + 1. หากx=0นั่นส่งผล1ให้ไม่ใช่ค่าที่ถูกต้อง: อินฟินิตี้
Cole Johnson

5
@ColeJohnson คำตอบที่ถูกต้องไม่ใช่อินฟินิตี้เช่นกัน (เว้นแต่คุณจะระบุว่าขีด จำกัด มาจากด้านใดด้านขวา = + inf ด้านซ้าย = -inf ไม่ระบุ = ไม่ได้กำหนดหรือ NaN)
Nick T

12
@ChrisHayes: นี่เป็นคำตอบที่ถูกต้องสำหรับคำถามที่ตระหนักว่าคำถามอาจเป็นปัญหา XY: meta.stackexchange.com/questions/66377/what-is-the-xy-problem
slebetman

17
@ColeJohnson การกลับมา0ไม่ใช่ประเด็นจริงๆ นี่คือสิ่งที่ OP ทำในคำถาม คุณสามารถใส่ข้อยกเว้นหรืออะไรก็ได้ที่เหมาะสมกับสถานการณ์ในส่วนนั้นของบล็อก หากคุณไม่ต้องการกลับมา0ก็ควรวิจารณ์คำถาม แน่นอนว่าการทำตามที่ OP ไม่ได้หมายความว่าจะมีการลงคะแนนให้กับคำตอบ คำถามนี้ไม่มีส่วนเกี่ยวข้องกับการคำนวณเพิ่มเติมหลังจากฟังก์ชันที่กำหนดเสร็จสิ้น 0สำหรับสิ่งที่คุณรู้ว่าความต้องการของโปรแกรมเลี่ยงการกลับมา
jpmc26

25

คุณจะไม่ได้รับการหารด้วยศูนย์โดยไม่คำนึงถึงค่าของ a - bเนื่องจากการหารจุดลอยตัวด้วย 0 จะไม่ทำให้เกิดข้อยกเว้น ส่งคืนอินฟินิตี้

ตอนนี้วิธีเดียวที่a == bจะคืนค่า true คือ if aและbมีบิตเดียวกัน หากแตกต่างกันเพียงบิตที่มีนัยสำคัญน้อยที่สุดความแตกต่างระหว่างพวกเขาจะไม่เป็น 0

แก้ไข:

ดังที่บัทเชบาแสดงความคิดเห็นอย่างถูกต้องมีข้อยกเว้นบางประการ:

  1. "ไม่ใช่ตัวเลขเปรียบเทียบ" เท็จกับตัวมันเอง แต่จะมีรูปแบบบิตเหมือนกัน

  2. -0.0 ถูกกำหนดให้เปรียบเทียบ true กับ +0.0 และรูปแบบบิตจะแตกต่างกัน

ดังนั้นถ้าทั้งสองaและbเป็นDouble.NaNคุณจะไปถึงส่วนคำสั่ง else แต่เนื่องจากNaN - NaNผลตอบแทนNaNด้วยคุณจะไม่ถูกหารด้วยศูนย์


11
Eran; ไม่เป็นความจริงอย่างเคร่งครัด "ไม่ใช่ตัวเลขเปรียบเทียบ" เท็จกับตัวมันเอง แต่จะมีรูปแบบบิตเหมือนกัน นอกจากนี้ -0.0 ยังถูกกำหนดให้เปรียบเทียบ true กับ +0.0 และรูปแบบบิตจะแตกต่างกัน
Bathsheba

1
@ บา ธ เชบาฉันไม่ได้พิจารณากรณีพิเศษเหล่านี้ ขอบคุณสำหรับความคิดเห็น
Eran

2
@ เอรันจุดที่ดีมากที่การหารด้วย 0 จะส่งกลับอินฟินิตี้ในจุดลอยตัว เพิ่มเข้าไปในคำถาม
Thirler

2
@Prashant แต่การหารจะไม่เกิดขึ้นในกรณีนี้เนื่องจาก a == b จะคืนค่าจริง
Eran

3
จริงๆแล้วคุณอาจได้รับข้อยกเว้น FP สำหรับการหารด้วยศูนย์ซึ่งเป็นตัวเลือกที่กำหนดโดยมาตรฐาน IEEE-754 แม้ว่าอาจไม่ใช่สิ่งที่คนส่วนใหญ่จะหมายถึงด้วย "ข้อยกเว้น";)
Voo

17

ไม่มีกรณีใดที่การหารด้วยศูนย์สามารถเกิดขึ้นได้ที่นี่

SMT Solver Z3สนับสนุน IEEE แม่นยำลอยคำนวณจุด ขอให้ Z3 หาตัวเลขaและbสิ่งนั้นa != b && (a - b) == 0:

(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)

UNSATผลที่ได้คือ ไม่มีตัวเลขดังกล่าว

สตริง SMTLIB ข้างต้นยังอนุญาตให้ Z3 เลือกโหมดการปัดเศษโดยพลการ ( rm) ซึ่งหมายความว่าผลลัพธ์จะมีไว้สำหรับโหมดการปัดเศษที่เป็นไปได้ทั้งหมด (ซึ่งมีห้าโหมด) ผลลัพธ์ยังรวมถึงความเป็นไปได้ที่ตัวแปรใด ๆ ในการเล่นอาจเป็นNaNหรือไม่มีที่สิ้นสุด

a == bจะดำเนินการเป็นfp.eqที่มีคุณภาพเพื่อให้+0fและ-0fเปรียบเทียบเท่ากับ การเปรียบเทียบกับศูนย์ถูกนำมาใช้fp.eqเช่นกัน เนื่องจากคำถามมีจุดมุ่งหมายเพื่อหลีกเลี่ยงการหารด้วยศูนย์นี่จึงเป็นการเปรียบเทียบที่เหมาะสม

หากการทดสอบความเท่าเทียมกันถูกนำไปใช้โดยใช้ความเท่าเทียมกันในระดับบิต+0fและ-0fน่าจะเป็นวิธีที่จะทำให้a - bเป็นศูนย์ คำตอบรุ่นก่อนหน้านี้ที่ไม่ถูกต้องมีรายละเอียดโหมดเกี่ยวกับกรณีนั้นสำหรับผู้ที่อยากรู้อยากเห็น

Z3 Onlineยังไม่รองรับทฤษฎี FPA ผลลัพธ์นี้ได้มาจากสาขาที่ไม่เสถียรล่าสุด สามารถทำซ้ำได้โดยใช้การผูก. NET ดังนี้:

var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
        context.MkNot(context.MkFPEq(aExpr, bExpr)),
        context.MkFPEq(subExpr, fpZero),
        context.MkTrue()
    );

var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);

var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);

var status = solver.Check();
Console.WriteLine(status);

ใช้ Z3 ที่จะตอบคำถามลอย IEEE เป็นสิ่งที่ดีเพราะมันเป็นเรื่องยากที่จะมองข้ามกรณี (เช่นNaN, -0f, +-inf) และคุณสามารถถามคำถามโดยพลการ ไม่จำเป็นต้องตีความและอ้างอิงข้อกำหนด คุณยังสามารถถามคำถามจำนวนทศนิยมและจำนวนเต็มแบบผสมเช่น " int log2(float)อัลกอริทึมเฉพาะนี้ถูกต้องหรือไม่"


คุณช่วยเพิ่มลิงค์ไปยัง SMT Solver Z3 และลิงค์ไปยังล่ามออนไลน์ได้ไหม แม้ว่าคำตอบนี้จะดูเหมือนถูกต้อง แต่ก็มีบางคนคิดว่าผลลัพธ์เหล่านี้ไม่ถูกต้อง
AL

12

ฟังก์ชันที่ให้มาสามารถคืนค่าอินฟินิตี้ได้:

public class Test {
    public static double calculation(double a, double b)
    {
         if (a == b)
         {
             return 0;
         }
         else
         {
             return 2 / (a - b);
         }
    }    

    /**
     * @param args
     */
    public static void main(String[] args) {
        double d1 = Double.MIN_VALUE;
        double d2 = 2.0 * Double.MIN_VALUE;
        System.out.println("Result: " + calculation(d1, d2)); 
    }
}

ผลลัพธ์คือResult: -Infinity.

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


6

ในการใช้จุดลอยตัวที่สอดคล้องกับ IEEE-754 ทศนิยมแต่ละประเภทสามารถเก็บตัวเลขได้สองรูปแบบ ค่าหนึ่ง ("normalized") ใช้สำหรับค่าทศนิยมส่วนใหญ่ แต่จำนวนที่น้อยที่สุดอันดับสองที่สามารถแสดงได้นั้นมีขนาดใหญ่กว่าค่าที่เล็กที่สุดเพียงเล็กน้อยเท่านั้นดังนั้นความแตกต่างระหว่างค่าเหล่านี้จึงไม่สามารถแสดงได้ในรูปแบบเดียวกัน รูปแบบอื่น ("denormalized") ใช้สำหรับตัวเลขขนาดเล็กมากที่ไม่สามารถแสดงได้ในรูปแบบแรกเท่านั้น

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

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


6

ในสมัยก่อนก่อน IEEE 754 ค่อนข้างเป็นไปได้ว่า a! = b ไม่ได้หมายความถึง ab! = 0 และในทางกลับกัน นั่นเป็นเหตุผลหนึ่งในการสร้าง IEEE 754 ตั้งแต่แรก

ด้วย IEEE 754 แทบจะรับประกันได้ คอมไพเลอร์ C หรือ C ++ ได้รับอนุญาตให้ดำเนินการด้วยความแม่นยำสูงกว่าที่จำเป็น ดังนั้นถ้า a และ b ไม่ใช่ตัวแปร แต่เป็นนิพจน์ (a + b)! = c ไม่ได้หมายความถึง (a + b) - c! = 0 เนื่องจาก a + b สามารถคำนวณได้ครั้งเดียวด้วยความแม่นยำที่สูงกว่าและไม่มี ความแม่นยำสูงขึ้น

FPU จำนวนมากสามารถเปลี่ยนไปใช้โหมดที่พวกเขาไม่ส่งคืนตัวเลขที่ถูกทำให้เป็นมาตรฐาน แต่แทนที่ด้วย 0 ในโหมดนั้นถ้า a และ b เป็นตัวเลขมาตรฐานขนาดเล็กที่ความแตกต่างนั้นน้อยกว่าตัวเลขมาตรฐานที่เล็กที่สุด แต่มากกว่า 0 ! = b ยังไม่รับประกันว่า a == b

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


2

ฉันนึกถึงกรณีที่คุณอาจทำให้สิ่งนี้เกิดขึ้นได้ นี่คือตัวอย่างที่คล้ายคลึงกันในฐาน 10 - จริงๆแล้วสิ่งนี้จะเกิดขึ้นในฐาน 2 แน่นอน

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

ลองนึกภาพเพื่อความสะดวกว่าเรามีหน่วยทศนิยมที่ทำงานในฐาน 10 และมีความแม่นยำ 3 หลัก จะเกิดอะไรขึ้นเมื่อคุณลบ 9.99 จาก 10.0?

1.00e2-9.99e1

Shift เพื่อให้แต่ละค่ามีเลขชี้กำลังเท่ากัน

1.00e2-0.999e2

ปัดเศษเป็น 3 หลัก

1.00e2-1.00e2

เอ่อโอ้!

ท้ายที่สุดแล้วสิ่งนี้จะเกิดขึ้นได้หรือไม่นั้นขึ้นอยู่กับการออกแบบ FPU เนื่องจากช่วงของเลขชี้กำลังสำหรับคู่มีขนาดใหญ่มากฮาร์ดแวร์จึงต้องปัดเศษภายในในบางจุด แต่ในกรณีข้างต้นตัวเลขพิเศษเพียง 1 หลักภายในจะป้องกันไม่ให้เกิดปัญหาใด ๆ


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

1
“ ในที่สุดสิ่งนี้จะเกิดขึ้นได้หรือไม่นั้นขึ้นอยู่กับการออกแบบ FPU” ไม่มันไม่สามารถเกิดขึ้นได้เพราะคำจำกัดความของ Java บอกว่าทำไม่ได้ การออกแบบ FPU ไม่มีส่วนเกี่ยวข้องใด ๆ
Pascal Cuoq

@PascalCuoq: แก้ไขฉันถ้าฉันผิด แต่strictfpไม่ได้เปิดใช้งานเป็นไปได้สำหรับการคำนวณเพื่อให้ได้ค่าที่เล็กเกินไปdoubleแต่จะพอดีกับค่าทศนิยมที่มีความแม่นยำสูง
supercat

@supercat ขาดstrictfpเท่านั้นที่มีอิทธิพลต่อค่าของ“ผลกลาง” และฉันกำลังข้อความจากdocs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 aและbเป็นdoubleตัวแปรไม่ใช่ผลลัพธ์ระดับกลางดังนั้นค่าของมันจึงเป็นค่าความแม่นยำสองเท่าดังนั้นจึงเป็นทวีคูณของ 2 ^ -1074 การลบค่าความแม่นยำสองเท่านี้จึงเป็นผลคูณของ 2 ^ -1074 ดังนั้นช่วงเลขชี้กำลังที่กว้างขึ้นจะเปลี่ยนคุณสมบัติที่ความแตกต่างคือ 0 iff a == b
Pascal Cuoq

@supercat สิ่งนี้สมเหตุสมผล - คุณต้องการเพียงหนึ่งบิตเพิ่มเติมเพื่อทำสิ่งนี้
Keldor314

1

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

ในการเปรียบเทียบการลอยตัวเพื่อความเท่าเทียมกันคุณต้องตรวจสอบว่าค่านั้น "ใกล้พอ" กับค่าเดียวกันหรือไม่:

if ((first >= second - error) || (first <= second + error)

6
"ไม่ควรเลย" นั้นค่อนข้างแรง แต่โดยทั่วไปแล้วนี่เป็นคำแนะนำที่ดี
Mark Pattison

1
ในขณะที่คุณเป็นจริงabs(first - second) < error(หรือ<= error) นั้นง่ายและรัดกุมกว่า
glglgl

3
แม้ว่าความจริงในกรณีส่วนใหญ่ ( ไม่ใช่ทั้งหมด ) ไม่ได้ตอบคำถามจริงๆ
milleniumbug

4
การทดสอบตัวเลขทศนิยมเพื่อความเท่าเทียมกันมักมีประโยชน์ ไม่มีอะไรที่ดีเกี่ยวกับการเปรียบเทียบกับ epsilon ที่ไม่ได้รับการคัดเลือกอย่างรอบคอบและมีเหตุผลน้อยกว่าที่จะเปรียบเทียบกับ epsilon เมื่อมีการทดสอบความเท่าเทียมกัน
tmyklebu

1
หากคุณจัดเรียงอาร์เรย์บนคีย์ทศนิยมฉันสามารถรับประกันได้ว่าโค้ดของคุณจะไม่ทำงานหากคุณพยายามใช้เทคนิคเปรียบเทียบตัวเลขทศนิยมกับ epsilon เนื่องจากการรับประกันว่า a == b และ b == c หมายถึง a == c ไม่มีอีกแล้ว สำหรับตารางแฮชปัญหาเดียวกันแน่นอน เมื่อความเท่าเทียมกันไม่ใช่สกรรมกริยาอัลกอริทึมของคุณก็พัง
gnasher729

1

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

ไม่แน่ใจว่านี่คือ C ++ หรือ Java เนื่องจากไม่มีแท็กภาษา

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}

1

ปัญหาหลักคือคอมพิวเตอร์แทนค่าคู่ (aka float หรือจำนวนจริงในภาษาคณิตศาสตร์) ผิดเมื่อคุณมีทศนิยม "มากเกินไป" เช่นเมื่อคุณจัดการกับ double ที่ไม่สามารถเขียนเป็นค่าตัวเลขได้ ( pi หรือผลลัพธ์ของ 1/3)

ดังนั้น a == b ไม่สามารถทำได้ด้วยค่าสองเท่าของ a และ b คุณจะจัดการกับ a == b ได้อย่างไรเมื่อ a = 0.333 และ b = 1/3? ขึ้นอยู่กับ OS ของคุณเทียบกับ FPU เทียบกับจำนวนเทียบกับภาษาเทียบกับจำนวน 3 หลัง 0 คุณจะมีจริงหรือเท็จ

อย่างไรก็ตามหากคุณทำการ "คำนวณค่าสองเท่า" บนคอมพิวเตอร์คุณต้องจัดการกับความถูกต้องดังนั้นแทนที่จะทำa==bคุณต้องทำabsolute_value(a-b)<epsilonและ epsilon จะสัมพันธ์กับสิ่งที่คุณกำลังสร้างแบบจำลองในเวลานั้นในอัลกอริทึมของคุณ คุณไม่สามารถมีค่า epsilon สำหรับการเปรียบเทียบคู่ทั้งหมดของคุณ

กล่าวโดยย่อเมื่อคุณพิมพ์ a == b คุณมีนิพจน์ทางคณิตศาสตร์ที่ไม่สามารถแปลบนคอมพิวเตอร์ได้ (สำหรับเลขทศนิยมใด ๆ )

PS: ครวญเพลงทุกสิ่งที่ฉันตอบที่นี่ยังมีการตอบสนองและความคิดเห็นของผู้อื่นไม่มากก็น้อย


1

จากการตอบกลับของ @malarres และความคิดเห็นของ @Taemyr นี่คือผลงานเล็กน้อยของฉัน:

public double calculation(double a, double b)
{
     double c = 2 / (a - b);

     // Should not have a big cost.
     if (isnan(c) || isinf(c))
     {
         return 0; // A 'whatever' value.
     }
     else
     {
         return c;
     }
}

ประเด็นของฉันคือบอกว่า: วิธีที่ง่ายที่สุดที่จะทราบว่าผลลัพธ์ของการหารเป็น nan หรือ inf นั้นเป็นจริงในการหาร

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