ทำไมการแปลงจำนวนเต็มเป็นจำนวนทศนิยมจึงสูญเสียความแม่นยำ: คณิตศาสตร์ที่ซ่อนอยู่เบื้องหลังตัวเลขในคอมพิวเตอร์

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

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

ความจริงที่น่าประหลาดใจเกี่ยวกับการแปลงตัวเลข

เมื่อคุณแปลงจำนวนเต็ม 32 บิตเป็น float 32 บิต คุณอาจสูญเสียความแม่นยำที่แน่นอน สิ่งนี้เกิดขึ้นเพราะทั้งสองประเภทข้อมูลใช้พื้นที่จัดเก็บเท่ากัน แต่แทนค่าชุดตัวเลขที่แตกต่างกันโดยสิ้นเชิง มีเพียงประมาณ 3.5% ของจำนวนเต็ม 32 บิตทั้งหมดที่สามารถแทนค่าได้อย่างแม่นยำเป็น float 32 บิต สถานการณ์จะแย่ลงกับตัวเลข 64 บิต ซึ่งมีเพียง 0.5% ของจำนวนเต็มที่สามารถแทนค่าได้อย่างสมบูรณ์แบบเป็น double

ปัญหานี้เกิดจากวิธีการทำงานของจำนวนจุดลอยตัว พวกมันใช้สัญกรณ์วิทยาศาสตร์ แบ่งบิตระหว่างตัวเลขจริงและเลขชี้กำลังที่กำหนดตำแหน่งจุดทศนิยม float 32 บิตใช้ 23 บิตสำหรับเก็บตัวเลขสำคัญ 8 บิตสำหรับเลขชี้กำลัง และ 1 บิตสำหรับเครื่องหมาย ซึ่งหมายความว่าจำนวนเต็มที่ต้องการความแม่นยำมากกว่า 23 บิตจะไม่สามารถใส่ได้อย่างแม่นยำ

สัญกรณ์วิทยาศาสตร์: วิธีการเขียนตัวเลขเป็นสัมประสิทธิ์คูณด้วยกำลังของ 10 (เช่น 1.23 × 10^5 สำหรับ 123,000)

สถิติการแปลงจำนวนเต็ม 32 บิตเป็นทศนิยม:

  • จำนวนเต็ม 32 บิตทั้งหมด: 2^32 (ประมาณ 4.3 พันล้าน)
  • สามารถแสดงได้อย่างแม่นยำในรูปแบบทศนิยม 32 บิต: 9 × 2^23 (ประมาณ 75 ล้าน)
  • เปอร์เซ็นต์ที่สามารถแสดงได้อย่างแม่นยำ: ~3.5%
  • บิตความแม่นยำใน float32: 23 บิต
  • บิตเลขชี้กำลังใน float32: 8 บิต

ผลกระทบในโลกจริงต่อการพัฒนาซอฟต์แวร์

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

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

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

ทำไมสิ่งนี้จึงสำคัญสำหรับภาษาโปรแกรมมิ่งที่แตกต่างกัน

ภาษาโปรแกรมมิ่งต่างๆ จัดการปัญหานี้ในรูปแบบที่หลากหลาย JavaScript ใช้เฉพาะ float 64 บิตสำหรับตัวเลขทั้งหมด ซึ่งสามารถแทนค่าจำนวนเต็ม 32 บิตได้อย่างแม่นยำ แต่ยังคงมีข้อจำกัดด้านความแม่นยำสำหรับค่าที่ใหญ่กว่า Python รองรับจำนวนเต็มขนาดไม่จำกัด ทำให้ปัญหาการแปลงชัดเจนยิ่งขึ้นเมื่อตัวเลขขนาดใหญ่เหล่านี้พบกับการดำเนินการจุดลอยตัว

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

ความแม่นยำ: จำนวนหลักสำคัญที่รูปแบบตัวเลขสามารถเก็บได้อย่างแม่นยำ

โครงสร้างรูปแบบ IEEE 754 Floating Point:

ส่วนประกอบ Float32 Float64
Sign bit 1 bit 1 bit
Exponent 8 bits 11 bits
Mantissa/Fraction 23 bits 52 bits
รวม 32 bits 64 bits
รูปแบบ ±1.f × 2^e ±1.f × 2^e

การทำความเข้าใจคณิตศาสตร์เบื้องหลังข้อจำกัด

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

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

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

อ้างอิง: Most ints are not floats