เรื่องหนึ่งที่โปรแกรมเมอร์มือใหม่มักจะไม่ระวังกันคือ การใช้ค่าเลขทศนิยมในคอมพิวเตอร์นั้นเป็นฐานสอง ซึ่งไม่ตรงตัวกับการใช้เลขทศนิยมในฐานสิบทั่วๆ ไป ในการเขียนโปรแกรมหนึ่งๆ ที่มีการเปรียบเทียบค่าเลขทศนิยมจึงอาจจะดูดีในการทดสอบ เช่นว่า
print (0.05 + 0.05 == 0.1)
... True
เมื่อการทำงานในขั้นต้นทำงานดี โปรแกรมเมอร์อาจจะเชื่อว่าส่วนนี้ทำงานถูกต้องดี จนกระทั่งการทำงานในแบบที่คล้ายๆ กันมีการทำซ้ำจำนวนมากๆ เช่น
d1 = 0.0
for i in range(1000):
d1 += 0.0001
print repr(d1)
... 1.0000000000000007
print (d1 == 1.0)
... False
ถึงตอนนี้โปรแกรมก็เริ่มทำงานไม่เป็นไปตามที่เราคิด และโปรแกรมเมอร์จำนวนมากก็เริ่มกุมขมับว่าทำไมมันไม่เวิร์ค ขณะที่โปรแกรมเมอร์อีกกลุ่มหนึ่งที่เข้าใจปัญหานี้ดี ก็ต้องใช้เทคนิคสารพัดเพื่อที่จะแปลงการปัดค่าทศนิยมอย่างนี้ให้ถูกต้อง
ใน Python 2.4 เป็นต้นมา มีการอิมพลีเมนต์เอกสาร PEP-327 ซึ่งเป็นสเปคของเลขทศนิยมฐานสิบ ที่ทำงานในส่วนนี้ได้ถูกต้องกว่า อย่างในตัวอย่างต่อไปนี้
dec1 = Decimal("0.0");
for i in range(1000):
dec1 += Decimal("0.001")
print repr(dec1)
... Decimal("1.000")
print (dec1 == Decimal("1.0"))
... True
เท่านี้เราก็จะได้การทำงานที่เป็นไปตามคาดโดยไม่ต้องกังวลความไม่แน่นอนของโปรแกรมอีกต่อไป
Comments
ความจริงการใช้เลขทศนิยม (ฐานสิบ) ในการคำนวนก็ใช่ว่าจะได้คำตอบถูกต้องนะครับ เพียงแต่มันจะ "ผิดในแบบที่คนเราผิดกัน" ปัญหาของเศษก็ยังมีอยู่ดีแต่มันจะปัดผิดแบบที่เราๆทำกันเท่านั้นเอง ถ้าจะเอาให้ถูกต้องแบบสุดๆนี่ต้องใช้ระบบจำนวนจริง (rational) คือเก็บเลขเป็นเศษส่วน (ตัวตั้งกับตัวหาร) เพื่อให้ไม่ต้องมีการปัดเศษเกิดขึ้นเลย
มีคนเสนอให้ python มีเลขจำนวนจริงใน PEP-239, 240 แต่ข้อเสนอถูก reject ไปด้วยเหตุผลเรื่อง computational resource ครับ
""" The performance implications of the fact that summing two rationals (which take O(M) and O(N) space respectively) gives a rational which takes O(M+N) memory space is just too troublesome. There are excellent Rational implementations in both pure Python and as extensions (e.g., gmpy), but they'll always be a "niche market" IMHO. Probably worth PEPping, not worth doing without Decimal -- which is the right way to represent sums of money, a truly major use case in the real world. """
และในงานที่มีการคำนวนเยอะๆและต้องการความรวดเร็ว ตัวเลขแบบ Float จะดีกว่า Decimal มากเพราะใช้ความสามารถของ FPU ได้เต็มที่ นอกจากนั้น Decimal ของ python ในตอนนี้ยัง implement ด้วย python เอง ซึ่งทำให้อืดมาก ซึ่งในอนาคตเค้าก็มีแผนจะ implement ด้วย c แทน
Decimal น่าจะมีประโยชน์กับคนทำงานกับ database มากกว่าคนทำงานวิทยาศาสตร์ครับ เพราะชัวร์กว่า (แต่ช้ากว่า)
sake - rational คือจำนวนตรรกยะครับ ไม่ใช่จำนวนจริง
lewcpe.com, @wasonliw
อ้อ อีกนิดคือสุดท้ายแล้วต่อให้ใช้จำนวนตรรกยะ ก็ยังไม่แม่นยำร้อยเปอร์เซนต์นะครับ เพราะไม่สามารถเก็บจำนวนอตรรกยะได้ถูกต้องอยู่ดี อันนี้เป็นปัญหาของคอมพิวเตอร์ดิจิตอลโดยพื้นฐาน
lewcpe.com, @wasonliw
ใช่ๆครับ จำผิด จำนวนจริง คือจำนวนเต็มบวก จำนวนตรรกยะคือจำนวนที่เขียนในรูปเศษส่วนได้
ถ้าเข้าใจไม่ผิดจำนวนอตรรกยะเช่น Pi มักจะมีค่าอยู่ในรูปของสมการนะครับไม่มีใครหาตัวเลขแน่นอนได้ คงไม่ใช่ปัญหาเฉพาะของ computer เป็นปัญหากับคนด้วยครับ
ขอเสริมหน่อยนะครับ ของผมใช้ ActivePython นะ มันต้อง ใส่
from decimal import Decimal
ก่อนน่ะ ถึงจะมี Decimal ให้ใช้ ไม่รู้ว่าใน Python ปกติ (จาก python.org) ต้อง import ก่อนหรือเปล่า พอดีเห็นใน code ตัวอย่าง มาถึงก็เรียกใช้เอาเลย เล่นเอาผมงงไปพักใหญ่ :-P
เป็นเหมือนกันทุกที่ครับ พอดีบรรทัด import ผมละไป ------ LewCPE
lewcpe.com, @wasonliw
ผมอ่านบทความนี้นานแล้วแหละ แต่ไม่ได้ให้ความสนใจ จนมาวันนึงเจอก่ะโปรเจ็คตัวเอง (ผมใช้ C#เขียน)ผมบวกของทศนิยมสองตัว มันกลับได้จุดเพิ่มขึ้นมา ไม่ทราบว่ามาจากไหน ทำให้ค่าที่ได้คลาดเคลื่อนเ็ป็นอันมาก ต้องหาจุดแก้กันพักไหญ่ เหนื่อยน่าดู งานนี้สอนให้รู้ว่าอย่าประมาท