คณิตศาสตร์เลขทศนิยมนั้นสอดคล้องกันใน C # หรือไม่ เป็นไปได้ไหม


155

ไม่นี่ไม่ใช่คำถาม"ทำไมจึงเป็น (1 / 3.0) * 3! = 1"

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

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

นี้เกิดขึ้นแม้กระทั่งในหมู่ประมวลผลว่า "ตาม" มาตรฐาน IEEE-754หลักเพราะบางหน่วยประมวลผล (คือ x86) ใช้ความแม่นยำขยายคู่ นั่นคือพวกเขาใช้การลงทะเบียน 80 บิตเพื่อทำการคำนวณทั้งหมดจากนั้นตัดเป็น 64- บิตหรือ 32 บิตซึ่งนำไปสู่ผลลัพธ์การปัดเศษที่แตกต่างกันกว่าเครื่องที่ใช้ 64- บิตหรือ 32- บิตสำหรับการคำนวณ

ฉันเห็นวิธีแก้ไขปัญหาออนไลน์หลายวิธี แต่ทั้งหมดสำหรับ C ++ ไม่ใช่ C #:

  • ปิดใช้งานโหมดขยายความแม่นยำสองเท่า (เพื่อให้การdoubleคำนวณทั้งหมดใช้ IEEE-754 64- บิต) โดยใช้_controlfp_s(Windows), _FPU_SETCW(Linux?) หรือfpsetprec(BSD)
  • เรียกใช้คอมไพเลอร์เดียวกันด้วยการตั้งค่าการปรับให้เหมาะสมที่สุดเสมอและกำหนดให้ผู้ใช้ทุกคนมีสถาปัตยกรรม CPU เดียวกัน (ไม่มีการเล่นข้ามแพลตฟอร์ม) เนื่องจาก "คอมไพเลอร์" ของฉันเป็นจริง JIT ซึ่งอาจปรับให้เหมาะสมแตกต่างกันทุกครั้งที่รันโปรแกรมฉันไม่คิดว่ามันเป็นไปได้
  • ใช้เลขคณิตจุดคงที่และหลีกเลี่ยงfloatและdoubleทั้งหมด decimalจะทำงานเพื่อจุดประสงค์นี้ แต่จะช้ากว่านี้มากและไม่มีSystem.Mathฟังก์ชันไลบรารีใดสนับสนุน

ดังนั้นนี่เป็นปัญหาใน C # หรือไม่ ถ้าฉันตั้งใจจะสนับสนุน Windows เท่านั้น (ไม่ใช่ Mono)

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

ถ้าไม่มีมีห้องสมุดใดบ้างที่จะช่วยให้การคำนวณจุดลอยตัวสอดคล้องกันหรือไม่


ฉันเคยเห็นคำถามนี้แต่คำตอบทุกคำตอบจะทำให้เกิดปัญหาซ้ำโดยไม่มีวิธีแก้ปัญหาหรือบอกว่า "ไม่สนใจ" ซึ่งไม่ใช่ตัวเลือก ฉันถามคำถามที่คล้ายกันใน gamedevแต่ (เพราะผู้ชม) คำตอบส่วนใหญ่ดูเหมือนจะมุ่งสู่ C ++
BlueRaja - Danny Pflughoeft

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

1
@ ปีเตอร์คุณรู้จักการจำลองจุดลอยตัวที่รวดเร็วสำหรับ. net หรือไม่
CodesInChaos

1
Java ประสบปัญหานี้หรือไม่
Josh

3
@Josh: Java มีstrictfpคำหลักซึ่งบังคับให้ทำการคำนวณทั้งหมดในขนาดที่ระบุ ( floatหรือdouble) มากกว่าขนาดที่ขยาย อย่างไรก็ตาม Java ยังคงมีปัญหามากมายกับการรองรับ IEE-754 ภาษาการเขียนโปรแกรมน้อยมาก (มาก, มาก) รองรับ IEE-754
porges

คำตอบ:


52

ฉันรู้ว่าไม่มีทางที่จะทำให้คะแนนลอยตัวปกติกำหนดไว้ใน. net JITter ได้รับอนุญาตให้สร้างรหัสที่ทำงานแตกต่างกันในแพลตฟอร์มที่แตกต่างกัน (หรือระหว่าง. net รุ่นต่าง ๆ ) ดังนั้นการใช้floats ปกติในรหัส. net ที่กำหนดขึ้นจึงเป็นไปไม่ได้

วิธีแก้ปัญหาที่ฉันพิจารณา:

  1. ใช้ FixedPoint32 ใน C # แม้ว่านี่จะไม่ยากเกินไป (ฉันมีการนำไปใช้งานแล้วเสร็จครึ่งหนึ่ง) ช่วงของค่าที่น้อยมากทำให้มันน่ารำคาญที่จะใช้ คุณต้องระวังตลอดเวลาดังนั้นคุณจึงไม่ล้นและไม่แม่นยำจนเกินไป ในที่สุดฉันพบว่ามันไม่ง่ายกว่าการใช้จำนวนเต็มโดยตรง
  2. ใช้ FixedPoint64 ใน C # ฉันพบว่ามันค่อนข้างยากที่จะทำ สำหรับการดำเนินการบางอย่างจำนวนเต็มระดับกลางของ 128 บิตจะเป็นประโยชน์ แต่. net ไม่ได้เสนอประเภทดังกล่าว
  3. ใช้ floatingpoint 32 บิตแบบกำหนดเอง การไม่มี BitScanReverse ภายในทำให้เกิดความรำคาญเล็กน้อยเมื่อใช้งานสิ่งนี้ แต่ปัจจุบันฉันคิดว่านี่เป็นเส้นทางที่มีแนวโน้มมากที่สุด
  4. ใช้รหัสเนทีฟสำหรับการดำเนินการทางคณิตศาสตร์ เกิดค่าใช้จ่ายในการโทรของผู้รับมอบสิทธิ์ในการดำเนินการทางคณิตศาสตร์ทุกครั้ง

ฉันเพิ่งเริ่มต้นใช้งานซอฟต์แวร์ของเลขทศนิยม 32 บิต สามารถเพิ่ม / การคูณได้ 70million ต่อวินาทีใน 2.66GHz i3 ของฉัน https://github.com/CodesInChaos/SoftFloat เห็นได้ชัดว่ามันยังไม่สมบูรณ์และมีบักกี้


2
มี BigInteger จำนวนเต็มขนาด "ไม่ จำกัด " ถึงแม้ว่าจะไม่เร็วเท่า int ดั้งเดิมหรือมีความยาวดังนั้นจึงมี. NET ให้ประเภทเช่นนี้ (สร้างขึ้นสำหรับ F # ฉันเชื่อว่า แต่สามารถใช้ใน C #)
Rune FS

ตัวเลือกหนึ่งคือGNU MP เสื้อคลุมสำหรับ NET มันเป็น wrapper รอบ ๆGNU Multiple Precision Libraryซึ่งรองรับจำนวนเต็มความแม่นยำ "ไม่ จำกัด ", rationals (เศษส่วน) และตัวเลขทศนิยม
โคลจอห์นสัน

2
หากคุณกำลังจะทำสิ่งเหล่านี้คุณอาจลองdecimalก่อนเพราะมันง่ายกว่ามาก เฉพาะในกรณีที่มันช้าเกินไปสำหรับงานในมือวิธีการอื่น ๆ จะคุ้มค่ากับการคิด
Roman Starkov

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

ตัวอย่าง: ตัวเลขเช่น 2 ^ 32 แสดงเป็น (เลขชี้กำลัง: 32, แมนทิสซา: 1) หากเราคูณนี่ด้วยจำนวนลอยอีกตัว (exp, man) ผลลัพธ์ก็คือ (exp +32, man * 1) สำหรับการแบ่งผลลัพธ์คือ (expo - 32, man * 1) คูณ mantissa ด้วย 1 ไม่เปลี่ยน mantissa ดังนั้นมันไม่สำคัญว่ามันจะมีกี่บิต
คดเคี้ยวไปมา

28

ข้อมูลจำเพาะ C # (§4.1.6ชนิดจุดลอยตัว) ช่วยให้การคำนวณจุดลอยตัวทำได้โดยใช้ความแม่นยำสูงกว่าผลลัพธ์ ดังนั้นไม่ฉันไม่คิดว่าคุณสามารถคำนวณได้โดยตรงใน. Net คนอื่นแนะนำวิธีแก้ปัญหาต่าง ๆ ดังนั้นคุณสามารถลองได้


9
ฉันเพิ่งรู้ว่าข้อกำหนด C # นั้นไม่สำคัญว่าจะมีการกระจายแอสเซมบลีที่คอมไพล์หรือไม่ มันสำคัญถ้าใครต้องการความเข้ากันได้ของแหล่งที่มา สิ่งที่สำคัญจริงๆคือข้อกำหนดของ CLR แต่ฉันค่อนข้างมั่นใจว่ามันเป็นหลักประกันที่อ่อนแอเช่นเดียวกับการรับประกัน C #
CodesInChaos

จะไม่ส่งไปที่doubleแต่ละครั้งหลังจากการดำเนินการตัดบิตที่ไม่ต้องการออกไปให้ได้ผลลัพธ์ที่สอดคล้องกันหรือไม่
IllidanS4 ต้องการโมนิก้ากลับ

2
@ IllidanS4 ฉันไม่คิดว่าจะรับประกันผลลัพธ์ที่สอดคล้องกัน
svick

14

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

http://www.math.utah.edu/~beebe/software/ieee/

หมายเหตุเกี่ยวกับจุดคงที่

หมายเลขจุดคงที่ไบนารียังสามารถทำงานได้ดีแทนจุดลอยตามที่เห็นได้จากการดำเนินการทางคณิตศาสตร์สี่ขั้นพื้นฐาน:

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

เลขฐานคงที่แบบไบนารีสามารถนำไปใช้กับชนิดข้อมูลจำนวนเต็มใด ๆ เช่น int, long และ BigInteger และประเภทที่ไม่สอดคล้องกับ CLS uint และ ulong

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

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];

5
คุณควรอัปโหลดสิ่งนี้ไปยังไซต์โปรเจ็กต์โอเพนซอร์ซเช่น sourceforge หรือ github สิ่งนี้ทำให้ง่ายต่อการค้นหาง่ายต่อการมีส่วนร่วมและง่ายต่อการใส่ประวัติย่อของคุณเป็นต้นนอกจากนี้เคล็ดลับซอร์สโค้ด (ไม่ต้องสนใจ): ใช้constแทนstaticค่าคงที่เพื่อให้คอมไพเลอร์สามารถปรับให้เหมาะสม ชอบฟังก์ชั่นสมาชิกเป็นฟังก์ชั่นแบบคงที่ (เพื่อให้เราสามารถเรียก, อดีตmyDouble.LeadingZeros()แทนIntDouble.LeadingZeros(myDouble)); พยายามหลีกเลี่ยงชื่อตัวแปรตัวอักษรตัวเดียว ( MultiplyAnyLengthตัวอย่างเช่นมี 9 ทำให้เป็นการยากมากที่จะติดตาม)
BlueRaja - Danny Pflughoeft

โปรดใช้ความระมัดระวังโดยใช้uncheckedและไม่ CLS ที่สอดคล้องกับประเภทเช่นulong, uintฯลฯ สำหรับวัตถุประสงค์ความเร็ว - เพราะพวกเขาจะใช้เพื่อไม่ค่อย JIT ไม่ได้เพิ่มประสิทธิภาพของพวกเขาเป็นอย่างจริงจังเพื่อให้การใช้พวกเขาสามารถจริงจะช้ากว่าการใช้แบบปกติเช่นและlong intนอกจากนี้ C # ยังมีตัวดำเนินการโอเวอร์โหลดซึ่งโครงการนี้จะได้รับประโยชน์อย่างมาก ในที่สุดมีการทดสอบหน่วยที่เกี่ยวข้องหรือไม่ นอกจากสิ่งเล็ก ๆ น้อย ๆ เหล่านี้แล้วงานที่น่าทึ่งของปีเตอร์นี่ก็น่าประทับใจอย่างน่าขัน!
BlueRaja - Danny Pflughoeft

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

2
สิ่งที่ตลกคือเมื่อฉันโพสต์ในบล็อกของคุณฉันไม่ได้สังเกตว่าบล็อกนั้นเป็นของคุณ ฉันเพิ่งตัดสินใจลองใช้ google + และใน C # spark มันแนะนำว่ารายการบล็อก ดังนั้นฉันจึงคิดว่า "เป็นเรื่องบังเอิญที่น่าทึ่งสำหรับเราสองคนที่จะเริ่มเขียนสิ่งนี้ในเวลาเดียวกัน" แต่แน่นอนว่าเรามีทริกเกอร์เหมือนกัน :)
CodesInChaos

1
เหตุใดจึงต้องย้ายสิ่งนี้ไปยัง Java Java strictfpแล้วได้รับประกันกำหนดคณิตศาสตร์จุดลอยผ่าน
พลวง

9

นี่เป็นปัญหาสำหรับ C # หรือไม่

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

ฉันสามารถใช้ System.Decimal ได้หรือไม่

ไม่มีเหตุผลที่คุณทำไม่ได้ แต่มันเป็นสุนัขที่ช้า

มีวิธีบังคับให้โปรแกรมของฉันทำงานด้วยความแม่นยำสองเท่าหรือไม่?

ใช่. เป็นเจ้าภาพรันไทม์ CLR ตัวเอง ; และคอมไพล์ในการเรียก / แฟล็ก nessecary ทั้งหมด (ซึ่งเปลี่ยนพฤติกรรมของเลขทศนิยม) ในแอพพลิเคชั่น C ++ ก่อนเรียก CorBindToRuntimeEx

มีห้องสมุดใดบ้างที่จะช่วยให้การคำนวณจุดลอยตัวสอดคล้องกันหรือไม่

ไม่ใช่ที่ฉันรู้

มีวิธีอื่นในการแก้ปัญหานี้หรือไม่?

ฉันได้จัดการปัญหานี้มาก่อนความคิดที่จะใช้QNumbers พวกเขาเป็นรูปแบบของ reals ที่เป็นจุดคงที่; แต่ไม่ใช่จุดคงที่ในฐาน -10 (ฐานสิบ) - ค่อนข้างฐาน -2 (ไบนารี); ด้วยเหตุนี้พื้นฐานทางคณิตศาสตร์กับพวกเขา (เพิ่ม, ย่อย, มัล, div) จะเร็วกว่าจุดคงที่ฐานไร้เดียงสา -10; โดยเฉพาะอย่างยิ่งถ้าnเหมือนกันสำหรับทั้งสองค่า (ซึ่งในกรณีของคุณมันจะเป็น) นอกจากนี้เนื่องจากเป็นส่วนสำคัญจึงมีผลลัพธ์ที่ชัดเจนในทุกแพลตฟอร์ม

โปรดจำไว้ว่า framerate ยังสามารถส่งผลกระทบต่อสิ่งเหล่านี้ได้ แต่มันก็ไม่ได้แย่และแก้ไขได้อย่างง่ายดายโดยใช้จุดประสาน

ฉันสามารถใช้ฟังก์ชันทางคณิตศาสตร์เพิ่มเติมกับ QNumbers ได้หรือไม่

ใช่ปัดเศษทศนิยมเพื่อทำสิ่งนี้ นอกจากนี้คุณควรใช้ตารางการค้นหาสำหรับฟังก์ชัน trig (sin, cos); เนื่องจากสิ่งเหล่านี้สามารถให้ผลลัพธ์ที่แตกต่างกันอย่างแท้จริงบนแพลตฟอร์มที่แตกต่างกัน - และหากคุณเขียนโค้ดให้ถูกต้อง


3
ไม่แน่ใจว่าคุณกำลังพูดถึงเรื่อง framerates เป็นปัญหา เห็นได้ชัดว่าคุณต้องการให้มีอัตราการอัปเดตคงที่ (ดูตัวอย่างที่นี่ ) - ไม่ว่าจะเป็นอัตราเดียวกันกับที่แสดงอัตราเฟรมหรือไม่ ตราบใดที่ความไม่ถูกต้องเหมือนกันในทุกเครื่องเราก็ดี ฉันไม่เข้าใจคำตอบที่สามของคุณเลย
BlueRaja - Danny Pflughoeft

@ BlueLaja: คำตอบ "มีวิธีบังคับให้โปรแกรมของฉันทำงานด้วยความแม่นยำสองเท่าหรือไม่" อาจเป็นจำนวนเงินที่จะนำมาใช้ใหม่ Common Language Runtime ซึ่งจะซับซ้อนมากหรือใช้การโทรแบบดั้งเดิมไปยัง C ++ DLL จากแอปพลิเคชัน C # ดังที่กล่าวไว้ในคำตอบของผู้ใช้ shelleybutterfly คิดว่า "QNumbers" เป็นเพียงตัวเลขจุดคงที่แบบไบนารีตามที่บอกไว้ในคำตอบของฉัน (ฉันไม่ได้เห็นจนกระทั่งตอนนี้ตัวเลขจุดคงที่แบบไบนารีถูกเรียกว่า "QNumbers")
Peter O.

@Pieter O. คุณไม่จำเป็นต้องปรับใช้รันไทม์อีกครั้ง เซิร์ฟเวอร์ที่ฉันทำงานที่ บริษัท ของฉันเป็นโฮสต์ของ CLR runtime เป็นแอปพลิเคชัน C ++ ดั้งเดิม (เช่น SQL Server) ฉันขอแนะนำให้คุณ google CorBindToRuntimeEx
Jonathan Dickinson

@ BlueRaja มันขึ้นอยู่กับเกมที่สงสัย การใช้ขั้นตอนการกำหนดเฟรมคงที่กับเกมทั้งหมดไม่ใช่ตัวเลือกที่เหมาะสม - เนื่องจากอัลกอริทึม AOE แนะนำเวลาแฝงเทียม ซึ่งยอมรับไม่ได้ในเช่น FPS
Jonathan Dickinson

1
@ โจนาธาน: นี่เป็นเพียงปัญหาในเกมเพียร์ทูเพียร์ที่ส่งอินพุตเท่านั้น - สำหรับสิ่งเหล่านี้คุณต้องมีอัตราการอัพเดทคงที่ FPS ส่วนใหญ่ไม่ทำงานเช่นนี้ แต่บางอย่างที่จำเป็นต้องมีอัตราการอัปเดตคงที่ ดูคำถามนี้
BlueRaja - Danny Pflughoeft

6

ตามรายการบล็อก MSDNเก่านี้เล็กน้อยJIT จะไม่ใช้ SSE / SSE2 สำหรับจุดลอยตัวมันคือ x87 ทั้งหมด ด้วยเหตุนี้ดังที่คุณกล่าวถึงคุณต้องกังวลเกี่ยวกับโหมดและแฟล็กและใน C # ที่ไม่สามารถควบคุมได้ ดังนั้นการใช้การดำเนินการจุดลอยตัวปกติจะไม่รับประกันผลลัพธ์ที่เหมือนกันทุกเครื่องสำหรับโปรแกรมของคุณ

เพื่อให้ได้ความแม่นยำซ้ำของความแม่นยำสองเท่าคุณจะต้องทำการจำลองจุดลอยตัว (หรือจุดตรึง) ฉันไม่รู้ห้องสมุด C # ที่จะทำสิ่งนี้

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

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

ปัญหาใหญ่ของ x87 คือการคำนวณอาจทำได้ในความแม่นยำ 53 บิตหรือ 64 บิตขึ้นอยู่กับการตั้งค่าความแม่นยำและการลงทะเบียนที่หกไปยังหน่วยความจำ แต่สำหรับการดำเนินการหลาย ๆ การการดำเนินการด้วยความแม่นยำสูงและการปัดเศษกลับไปสู่ความแม่นยำที่ต่ำกว่าจะรับประกันคำตอบที่ถูกต้องซึ่งหมายความว่าคำตอบนั้นจะรับประกันว่าจะเหมือนกันในทุกระบบ ไม่ว่าคุณจะได้รับความแม่นยำที่เพิ่มขึ้นจะไม่สำคัญเพราะคุณมีความแม่นยำเพียงพอที่จะรับประกันคำตอบที่ถูกต้องในทั้งสองกรณี

การดำเนินการที่ควรทำงานในรูปแบบนี้: การบวกการลบการคูณการหาร sqrt สิ่งต่าง ๆ เช่นบาปประสบการณ์ ฯลฯ จะไม่ทำงาน (ผลลัพธ์มักจะตรงกัน แต่ไม่มีการรับประกัน) "การปัดเศษสองครั้งไม่มีอันตรายเมื่อใด?" การอ้างอิง ACM (การชำระเงินต้องใหม่)

หวังว่านี่จะช่วยได้!


2
นอกจากนี้ยังเป็นปัญหาที่. NET 5 หรือ 6 หรือ 42 อาจไม่ใช้โหมดการคำนวณ x87 อีกต่อไป ไม่มีอะไรในมาตรฐานที่ต้องการมัน
Eric J.

5

ตามที่ระบุไว้แล้วโดยคำตอบอื่น ๆ : ใช่นี่เป็นปัญหาใน C # - แม้ในขณะที่อยู่ใน Windows ที่บริสุทธิ์

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

ตามที่ร้องขอโดย OP - เกี่ยวกับประสิทธิภาพ:

System.Decimalแทนตัวเลขด้วย 1 บิตสำหรับเครื่องหมายและจำนวนเต็ม 96 บิตและ "สเกล" (แสดงถึงตำแหน่งทศนิยม) สำหรับการคำนวณทั้งหมดคุณต้องดำเนินการกับโครงสร้างข้อมูลนี้และไม่สามารถใช้คำสั่ง floating point ใด ๆ ที่อยู่ภายใน CPU

BigInteger"การแก้ปัญหา" ทำอะไรบางอย่างที่คล้ายกัน - เดียวที่คุณสามารถกำหนดวิธีการที่ตัวเลขที่คุณต้องการ / ต้องการ ... บางทีคุณอาจต้องการเพียง 80 บิตหรือ 240 บิตของความแม่นยำ

ความเชื่องช้ามาจากการจำลองการทำงานทั้งหมดในหมายเลขเหล่านี้ผ่านคำแนะนำเฉพาะจำนวนเต็มเท่านั้นโดยไม่ต้องใช้คำสั่ง CPU / FPU ในตัวซึ่งจะนำไปสู่คำแนะนำเพิ่มเติมต่อการดำเนินการทางคณิตศาสตร์

เพื่อลดประสิทธิภาพการเข้าชมมีกลยุทธ์หลายอย่าง - เช่น QNumbers (ดูคำตอบจาก Jonathan Dickinson - คณิตศาสตร์เลขลอยตัวสอดคล้องกันใน C # หรือไม่? ) และ / หรือแคช (เช่นการคำนวณตรีโกณฯ ... ) เป็นต้น


1
โปรดทราบว่าBigIntegerมีเฉพาะใน. Net 4.0
svick

ฉันเดาว่าผลการปฏิบัติงานที่ได้รับของBigIntegerเกินกว่าประสิทธิภาพการทำงานที่ได้รับจากทศนิยม
CodesInChaos

สองสามครั้งในคำตอบที่นี่มีการอ้างอิงถึงประสิทธิภาพของการใช้Decimal(@Jonathan Dickinson - 'dog slow') หรือBigInteger(@CodeInChaos ความคิดเห็นด้านบน) - ใครช่วยกรุณาอธิบายเล็กน้อยเกี่ยวกับประสิทธิภาพการทำงานเหล่านี้และเป็นไปได้หรือไม่ / ทำไมพวกเขาถึงหยุดยั้งการแสดงวิธีแก้ปัญหา
Barry Kaye

@YAHIA - ขอบคุณสำหรับการแก้ไข - การอ่านที่น่าสนใจอย่างไรก็ตามคุณสามารถช่วยให้คุณคาดเดาการเล่นบอลที่ไม่ได้ใช้ 'โฟลต' ได้หรือเปล่าเรากำลังพูดถึง 10% ช้าลงหรือช้าลง 10 เท่า ต้องการรู้สึกถึงลำดับความสำคัญโดยนัย
Barry Kaye

มันมีความคล้ายคลึงกันมากกว่าในพื้นที่ 1: 5 มากกว่า "เพียง 10%"
Yahia

2

ต่อไปนี้เป็นความพยายามครั้งแรกของฉันในการทำสิ่งนี้ :

  1. สร้างโครงการ ATL.dll ที่มีวัตถุง่าย ๆ ในนั้นเพื่อใช้สำหรับการดำเนินการจุดลอยตัวที่สำคัญของคุณ ตรวจสอบให้แน่ใจว่าได้รวบรวมด้วยค่าสถานะที่ปิดใช้งานโดยใช้ฮาร์ดแวร์ที่ไม่ใช่ xx87 เพื่อทำทศนิยม
  2. สร้างฟังก์ชั่นที่เรียกใช้การดำเนินการจุดลอยตัวและส่งคืนผลลัพธ์ เริ่มต้นง่ายๆแล้วถ้ามันใช้งานได้สำหรับคุณคุณสามารถเพิ่มความซับซ้อนเพื่อตอบสนองความต้องการด้านประสิทธิภาพในภายหลังได้หากจำเป็น
  3. วางการเรียก control_fp รอบ ๆ คณิตศาสตร์ที่แท้จริงเพื่อให้แน่ใจว่ามันจะทำแบบเดียวกันกับทุกเครื่อง
  4. อ้างอิงไลบรารีใหม่ของคุณและทดสอบเพื่อให้แน่ใจว่าทำงานได้ตามที่คาดไว้

(ฉันเชื่อว่าคุณสามารถรวบรวมเป็น 32- บิต .dll จากนั้นใช้กับ x86 หรือ AnyCpu [หรือมีแนวโน้มเพียงกำหนดเป้าหมาย x86 บนระบบ 64 บิตดูความคิดเห็นด้านล่าง])

ถ้าสมมุติว่ามันใช้งานได้คุณควรใช้ Mono ฉันคิดว่าคุณน่าจะสามารถทำซ้ำไลบรารี่บนแพลตฟอร์ม x86 อื่น ๆ ในลักษณะที่คล้ายกัน (ไม่ใช่ COM แน่นอนแม้ว่าอาจจะมีไวน์หรือไม่ออกจากพื้นที่ของฉันเพียงครั้งเดียว เราไปที่นั่น ... )

สมมติว่าคุณสามารถใช้งานได้คุณควรจะสามารถตั้งค่าฟังก์ชั่นที่กำหนดเองที่สามารถทำงานได้หลายอย่างพร้อมกันเพื่อแก้ไขปัญหาด้านประสิทธิภาพและคุณจะมีเลขทศนิยมที่ช่วยให้คุณได้ผลลัพธ์ที่สอดคล้องกันในแพลตฟอร์มที่มีจำนวนน้อยที่สุด ของรหัสที่เขียนใน C ++ และปล่อยให้ส่วนที่เหลือของรหัสของคุณใน C #


"คอมไพล์ไฟล์. 32 บิตแล้วใช้ ... AnyCpu" ฉันคิดว่านี่จะใช้ได้เมื่อทำงานบนระบบ 32 บิตเท่านั้น บนระบบ 64 บิตเฉพาะโปรแกรมที่กำหนดเป้าหมายx86จะสามารถโหลด 32 บิต dll ได้
CodesInChaos

2

ฉันไม่ใช่นักพัฒนาเกมถึงแม้ว่าฉันจะมีประสบการณ์มากมายกับปัญหาที่ยากในการคำนวณ ... ดังนั้นฉันจะทำให้ดีที่สุด

กลยุทธ์ที่ฉันจะนำมาใช้เป็นหลักนี้:

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

ความยาวย่อของสิ่งนี้คือคุณต้องหายอดเงินคงเหลือ หากคุณใช้การเรนเดอร์ 30ms (~ 33fps) และเพียง 1ms ทำการตรวจจับการชน (หรือแทรกการดำเนินการที่มีความไวสูงอื่น ๆ ) - แม้ว่าคุณจะใช้เวลาสามเท่าในการคำนวณทางคณิตศาสตร์ที่สำคัญ คุณลดลงจาก 33.3fps เป็น 30.3fps

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


1

การตรวจสอบลิงก์ในคำตอบอื่น ๆ ทำให้ชัดเจนว่าคุณจะไม่รับประกันว่าจะมีการใช้ "ถูกต้อง" หรือไม่หรือคุณจะได้รับความแม่นยำแน่นอนสำหรับการคำนวณที่กำหนด แต่บางทีคุณอาจพยายามอย่างดีที่สุดด้วย (1) การตัดการคำนวณทั้งหมดให้น้อยที่สุด (เช่นหากการใช้งานที่แตกต่างกันจะให้ความแม่นยำ 32 ถึง 80 บิตให้ตัดการทำงานทุกครั้งเป็น 30 หรือ 31 บิต) (2) มีตารางกรณีทดสอบสองสามรายการเมื่อเริ่มต้น (กรณีเส้นขอบของการเพิ่ม, ลบ, คูณ, หาร, sqrt, cosine, ฯลฯ ) และหากการใช้งานคำนวณค่าที่ตรงกับตารางแล้วไม่รบกวนการปรับเปลี่ยนใด ๆ


ตัดการดำเนินการทุกครั้งเป็น 30 หรือ 31 บิตเสมอ - นี่คือสิ่งที่floatประเภทข้อมูลทำบนเครื่อง x86 - อย่างไรก็ตามสิ่งนี้จะทำให้ผลลัพธ์ที่แตกต่างจากเครื่องที่คำนวณโดยใช้เพียง 32 บิตเท่านั้นและการเปลี่ยนแปลงเล็ก ๆ เหล่านี้จะเผยแพร่ไปตามกาลเวลา ดังนั้นคำถาม
BlueRaja - Danny Pflughoeft

หาก "ความแม่นยำ N บิต" หมายถึงการคำนวณใด ๆ ที่ถูกต้องกับหลาย ๆ บิตและเครื่อง A นั้นมีความแม่นยำถึง 32 บิตในขณะที่เครื่อง B มีความแม่นยำถึง 48 บิตจากนั้น 32 บิตแรกของการคำนวณใด ๆ จะไม่ตัดทอนเป็น 32 บิตหรือน้อยกว่าหลังจากการดำเนินการทุกครั้งทำให้ทั้งสองเครื่องตรงกันกันหรือไม่ ถ้าไม่เป็นตัวอย่างอะไร
พยานคุ้มครอง ID 44583292

-3

คำถามของคุณในสิ่งที่ค่อนข้างยากและทางเทคนิค O_o อย่างไรก็ตามฉันอาจมีความคิด

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

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

'ความผิดพลาด' ที่เกิดจากการปรับการปัดเศษจะเพิ่มขึ้นในแต่ละคำแนะนำเพิ่มเติม

ในฐานะที่เป็นตัวอย่างเราสามารถพูดได้ว่าในระดับการประกอบ: a * b * c ไม่เทียบเท่ากับ * c * b

ฉันไม่แน่ใจทั้งหมดคุณจะต้องถามคนที่รู้จักสถาปัตยกรรม CPU มากกว่าฉัน: p

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

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

ฉันหวังว่าฉันชัดเจนเพียงพอฉันไม่เก่งภาษาอังกฤษ (... เลย: s)


9
ลองนึกภาพนักกีฬา P2P คุณยิงใส่ผู้ชายคุณตีเขาแล้วเขาก็ตาย แต่มันใกล้มากคุณเกือบจะพลาด ในพีซีของอีกฝ่ายใช้การคำนวณที่แตกต่างกันเล็กน้อยและคำนวณว่าคุณพลาด คุณเห็นปัญหาตอนนี้หรือไม่? ในกรณีนี้การเพิ่มขนาดของรีจิสเตอร์จะไม่ช่วย (อย่างน้อยก็ไม่สมบูรณ์) การใช้การคำนวณเดียวกันที่แน่นอนในคอมพิวเตอร์แต่ละเครื่องจะ
svick

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

1
ใช่แล้วฉันไม่ได้คิดเกี่ยวกับสถานการณ์แบบนี้ อย่างไรก็ตามฉันเห็นด้วยกับ @CodeInChaos ในอันนี้ ฉันไม่พบว่าฉลาดจริง ๆ ในการตัดสินใจที่สำคัญสองครั้ง นี่เป็นปัญหาสถาปัตยกรรมซอฟต์แวร์มากกว่า หนึ่งโปรแกรมแอปพลิเคชั่นของนักกีฬาสำหรับตัวอย่างควรทำการคำนวณและส่งผลลัพธ์ไปให้ผู้อื่น คุณจะไม่มีข้อผิดพลาดด้วยวิธีนี้ คุณมีการเข้าชมหรือไม่ แต่เพียงคนเดียวที่จะสืบทอด ชอบพูดว่า @driushkin
AxFab

2
@Aesgar: ใช่นั่นเป็นวิธีที่นักกีฬาส่วนใหญ่ทำงาน; "ผู้มีอำนาจ" นั้นเรียกว่าเซิร์ฟเวอร์และเราเรียกสถาปัตยกรรมโดยรวมว่าเป็นสถาปัตยกรรม "ไคลเอนต์ / เซิร์ฟเวอร์" อย่างไรก็ตามมีสถาปัตยกรรมอีกประเภทหนึ่ง: peer-to-peer ใน P2P ไม่มีเซิร์ฟเวอร์ แต่ลูกค้าทั้งหมดจะต้องตรวจสอบการกระทำทั้งหมดด้วยกันก่อนที่จะเกิดอะไรขึ้น สิ่งนี้เพิ่มความล่าช้าทำให้ไม่เป็นที่ยอมรับสำหรับนักกีฬา แต่ลดปริมาณการใช้เครือข่ายลงอย่างมากทำให้เหมาะสำหรับเกมที่ยอมรับความล่าช้าเล็กน้อย (~ 250ms) แต่การซิงค์สถานะเกมทั้งหมดไม่ได้ กล่าวคือเกม RTS เช่น C&C และ Starcraft ใช้ P2P
BlueRaja - Danny Pflughoeft

5
ในเกม p2p คุณไม่มีเครื่องที่ไว้ใจได้ หากคุณอนุญาตให้สถานีหนึ่งตัดสินใจว่ากระสุนของเขาหรือไม่คุณเปิดโอกาสในการโกงลูกค้า นอกจากนี้ลิงก์ไม่สามารถแม้แต่จะจัดการกับปริมาณข้อมูลที่บางครั้งเป็นผลลัพธ์ - เกมทำงานโดยการส่งคำสั่งซื้อแทนที่จะเป็นผลลัพธ์ ฉันเล่นเกม RTS และหลายต่อหลายครั้งที่ฉันเห็นขยะที่ลอยอยู่รอบ ๆ มันไม่มีทางที่มันจะถูกส่งไปยังโฮมอัพลิงก์ปกติ
Loren Pechtel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.