ความไม่เสมอภาคที่เกิดจากความไม่ถูกต้องลอย


15

อย่างน้อยใน Java ถ้าฉันเขียนรหัสนี้:

float a = 1000.0F;
float b = 0.00004F;
float c = a + b + b;
float d = b + b + a;
boolean e = c == d;

ค่าของจะลิตรsอี ฉันเชื่อว่าสิ่งนี้เกิดจากข้อเท็จจริงที่ว่าการลอยตัวนั้นมีข้อ จำกัด อย่างมากในการแสดงตัวเลขอย่างแม่นยำ แต่ฉันไม่เข้าใจว่าทำไมแค่การเปลี่ยนตำแหน่งของaอาจทำให้ความไม่เท่าเทียมนี้เกิดขึ้นefalsea

ฉันลด s เป็นหนึ่งในทั้งบรรทัด 3 และ 4 ดังด้านล่างค่าของeอย่างไรก็ตามกลายเป็นt r u e :betrue

float a = 1000.0F;
float b = 0.00004F;
float c = a + b;
float d = b + a;
boolean e = c == d;

เกิดอะไรขึ้นในบรรทัดที่ 3 และ 4 ทำไมการดำเนินการเพิ่มเติมด้วยการลอยตัวจึงไม่เชื่อมโยงกัน?

ขอบคุณล่วงหน้า.


16
ตามตัวอย่างของคุณแสดงว่าการเติมจุดลอยตัวเป็นแบบสลับ แต่มันไม่เชื่อมโยงกัน
Yuval Filmus

1
ฉันแนะนำให้คุณค้นหาคำจำกัดความพื้นฐาน โปรดทราบว่าคอมไพเลอร์แยกวิเคราะห์เป็น( r + s ) + t (เพิ่มการเชื่อมโยงไปทางซ้าย) r+s+t(r+s)+t
Yuval Filmus

2
หาวิธีที่ง่ายเพื่อดูว่าทำไมนี้ควรจะเป็นเช่นนั้นพิจารณาXเป็นจำนวนมากและจำนวนน้อยมากเช่นว่าY X + Y = Xที่นี่X + Y + -Xจะเป็นศูนย์ แต่X + -X + Yจะเป็นYอย่างไร
David Schwartz


1
@J ... และสิ่งที่ทุกโปรแกรมเมอร์ควรรู้เกี่ยวกับจุดลอยเลขคณิต
Gilles 'หยุดความชั่วร้าย'

คำตอบ:


20

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

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

รับ : bเป็นจำนวนจุดลอยตัว ด้วยเลขฐานสองลอย2 bก็เป็นจำนวนจุดลอยตัว (เลขชี้กำลังมีค่ามากกว่าหนึ่ง) ดังนั้นจึงเพิ่มb + bโดยไม่มีข้อผิดพลาดในการปัดเศษ จากนั้นถูกเพิ่มเข้าไปที่แน่นอนคุ้มค่าB + B ผลลัพธ์ที่ได้คือค่าที่แน่นอน2 b + aถูกปัดเศษเป็นจำนวนทศนิยมที่ใกล้ที่สุดb+b+ab2bb+bab+b2b+a

ใช้+ B + B : + Bจะมีการเพิ่มและจะมีข้อผิดพลาดในการปัดเศษRดังนั้นเราจึงได้รับผล+ B + R เพิ่มbและผลลัพธ์คือค่าที่แน่นอน2 b + a + r , ปัดเศษเป็นจำนวนจุดลอยตัวที่ใกล้ที่สุดa++a+Ra++R2+a+R

ดังนั้นในกรณีหนึ่งถูกปัดเศษ ในอีกกรณีหนึ่ง2 b + a + rถูกปัดเศษ2+a2+a+R

PS ไม่ว่าสำหรับการคำนวณทั้งสองหมายเลขและbทั้งสองจะให้ผลลัพธ์เหมือนกันหรือไม่ขึ้นอยู่กับตัวเลขและข้อผิดพลาดในการปัดเศษในการคำนวณa + bและมักจะยากที่จะทำนาย การใช้ความแม่นยำเดี่ยวหรือสองครั้งไม่ได้สร้างความแตกต่างให้กับปัญหาในหลักการ แต่เนื่องจากข้อผิดพลาดในการปัดเศษจะแตกต่างกันจะมีค่าของ a และ b ซึ่งในความแม่นยำเดี่ยวผลลัพธ์จะเท่ากันและในความแม่นยำสองเท่า ความแม่นยำจะสูงขึ้นมาก แต่ปัญหาที่สองนิพจน์นั้นมีความเหมือนกันทางคณิตศาสตร์ แต่ไม่เหมือนกันในการคำนวณแบบเลขทศนิยมจะยังคงเหมือนเดิมaa+

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

PPPS ความคิดเห็นถามว่าเราควรถามว่าเลขทศนิยมนั้นเท่ากันหรือไม่ แน่นอนถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ ตัวอย่างเช่นถ้าคุณเรียงลำดับอาร์เรย์หรือนำชุดมาใช้คุณจะมีปัญหามากหากคุณต้องการใช้แนวคิด "ประมาณเท่ากัน" ในอินเตอร์เฟซผู้ใช้แบบกราฟิกคุณอาจจำเป็นต้องขนาดวัตถุคำนวณใหม่ถ้าขนาดของวัตถุที่มีการเปลี่ยนแปลง - คุณเปรียบเทียบ oldSize == newSize เพื่อหลีกเลี่ยงการคำนวณใหม่ที่รู้ว่าในทางปฏิบัติคุณแทบจะไม่เคยมีขนาดเหมือนกันเกือบและโปรแกรมของคุณถูกต้องแม้ว่าจะมีการคำนวณซ้ำที่ไม่จำเป็น


ในกรณีพิเศษนี้ b กลายเป็นระยะเมื่อแปลงเป็นไบนารีดังนั้นจึงมีข้อผิดพลาดในการปัดเศษทุกที่
André Souza Lemos

1
@ AndréSouzaLemos bในคำตอบนี้ไม่ใช่ 0.00004 เป็นสิ่งที่คุณได้รับหลังจากการแปลงและปัดเศษ
Alexey Romanov

"ในการใช้งานจุดลอยทั่วไปผลลัพธ์ของการดำเนินการครั้งเดียวถูกสร้างขึ้นราวกับว่าการดำเนินการถูกดำเนินการด้วยความแม่นยำอย่างไม่สิ้นสุด เมื่อฉันพยายามที่จะใช้งานจริงในแง่ของประตูตรรกะ (โปรแกรมจำลองสามารถจัดการกับบัส 64 บิตได้)
John Dvorak

คำถามไร้เดียงสา: การทดสอบความเสมอภาคแบบลอยทำให้เหมาะสมหรือไม่? เหตุใดภาษาการเขียนโปรแกรมส่วนใหญ่จึงอนุญาตให้มีการทดสอบ aa == b โดยที่ทั้งสองหรือหนึ่งเป็นทศนิยม
อยากรู้อยากเห็น _cat

คำจำกัดความที่เกี่ยวข้องจากวิกิพีเดีย: " Machine Epsilonให้ขอบเขตบนกับข้อผิดพลาดที่สัมพันธ์กันเนื่องจากการปัดเศษในการคำนวณเลขทศนิยม"
Blackhawk

5

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

จำนวนจุดลอยตัวประกอบด้วยเครื่องหมาย, mantissa (ความกว้างคงที่) และเลขชี้กำลัง (ความกว้างคงที่) เช่นนี้:

+/-  1.0101010101 × 2^12345
sign   ^mantissa^     ^exp^

สัญลักษณ์ทางวิทยาศาสตร์ปกติมีรูปแบบที่คล้ายกัน:

+/- 1.23456 × 10^99

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


ตัวอย่าง

เพื่อแสดงให้เห็นว่าเราใช้ตัวเลข 3 หลักหลังจุดทศนิยม

a = 99990 = 9.999 × 10^4
b =     3 = 3.000 × 10^0

(a + b) + b

ตอนนี้เราคำนวณ:

c = a + b
  = 99990 + 3      (exact)
  = 99993          (exact)
  = 9.9993 × 10^4  (exact)
  = 9.999 × 10^4.  (rounded to nearest)

ในขั้นตอนต่อไปแน่นอน:

d = c + b
  = 99990 + 3 = ...
  = 9.999 × 10^4.  (rounded to nearest)

ดังนั้น (A + B) + B = 9.999 × 10 4

(b + b) + a

แต่ถ้าเราทำการดำเนินการตามลำดับอื่น:

e = b + b
  = 3 + 3  (exact)
  = 6      (exact)
  = 6.000 × 10^0.  (rounded to nearest)

ต่อไปเราคำนวณ:

f = e + a
  = 6 + 99990      (exact)
  = 99996          (exact)
  = 9.9996 × 10^4  (exact)
  = 1.000 × 10^5.  (rounded to nearest)

ดังนั้น (b + b) + a = 1.000 × 10 5ซึ่งแตกต่างจากคำตอบอื่น ๆ ของเรา


5

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

0.0000410=0,00000000000000101001111100010110101100010001110001101101000111 ...2=[1]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0.0000410=1111101000,00000000000000101001111100010110101100010001110001101101000111 ...2=[1]11110100000000000000000101001111100010110101100010001110001101101000111 ...2×29

ชิ้นส่วนสีแดงคือตั๊กแตนตำข้าวตามที่เป็นจริง (ก่อนการปัดเศษ)

(100010+0.0000410)+0.0000410(0.0000410+0.0000410)+100010


0

เมื่อเร็ว ๆ นี้เราพบปัญหาการปัดเศษที่คล้ายกัน คำตอบที่กล่าวถึงข้างต้นนั้นถูกต้อง แต่ค่อนข้างมีเทคนิค

ฉันพบว่าสิ่งต่อไปนี้เป็นคำอธิบายที่ดีว่าทำไมข้อผิดพลาดในการปัดเศษจึงมีอยู่ http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR: จุดลอยตัวแบบไบนารี่ไม่สามารถแมปได้อย่างแม่นยำกับทศนิยมแบบทศนิยม สิ่งนี้ทำให้เกิดความไม่ถูกต้องที่สามารถรวมระหว่างการดำเนินการทางคณิตศาสตร์

ตัวอย่างการใช้ตัวเลขทศนิยมแบบทศนิยม: 1/3 + 1/3 + 1/3 จะเท่ากับ 1 อย่างไรก็ตามในทศนิยม: 0.333333 + 0.333333 + 0.333333 จะไม่เท่ากับ 1.000000 อย่างแน่นอน

สิ่งเดียวกันนี้เกิดขึ้นเมื่อทำการดำเนินการทางคณิตศาสตร์กับทศนิยมแบบเลขฐานสอง

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