Python Fraction: เก็บอัตราส่วนให้ตรงตั้งแต่รับค่า
สูตรอาหารหรือการแบ่งงบที่เริ่มจาก 0.1 + 0.2 แล้วเอาผลไปเทียบ 0.3 มักเจอเศษเล็ก ๆ จากเลขฐานสองครับ ถ้าความหมายของ input คืออัตราส่วนแน่นอน เช่น หนึ่งในสิบ ให้เก็บความหมายนั้นตั้งแต่ต้นด้วย fractions.Fraction แทนการหวังว่าจะแก้ปลายทางด้วยการปัดทศนิยม
จุดที่พลาดง่ายคือ Fraction(0.1) รับค่า float ที่ถูกปัดไปแล้ว จึงเก็บเศษส่วนของค่า binary float นั้นอย่างแม่นยำ ไม่ใช่ 1/10 ที่ผู้ใช้พิมพ์ ต่างจาก Fraction("0.1") ซึ่งอ่าน decimal string ตามความหมายของข้อความ ตัวอย่างต่อไปแสดงทั้งสองทางและทดสอบขอบเขตการประมาณ
import unittest
from fractions import Fraction
def share_from_text(text: str) -> Fraction:
return Fraction(text)
class FractionTests(unittest.TestCase):
def test_decimal_text_is_exact(self):
self.assertEqual(share_from_text("0.1") + share_from_text("0.2"), Fraction(3, 10))
self.assertNotEqual(Fraction(0.1), Fraction(1, 10))
def test_limit_denominator_is_approximation(self):
self.assertEqual(Fraction(0.1).limit_denominator(10), Fraction(1, 10))
self.assertEqual(Fraction(3, 7) * 7, 3)
def test_bad_denominator(self):
with self.assertRaises(ZeroDivisionError):
Fraction(1, 0)
if __name__ == "__main__":
unittest.main()
เลือกชนิดข้อมูลตามสัญญา
ถ้าค่าเป็นส่วนแบ่ง, อัตราทด หรือจำนวนชิ้นที่ต้องเทียบตรง Fraction ทำให้การบวก คูณ และย่อเศษส่วนถูกต้องตามเลขตรรกยะ เหมือนชั่งส่วนผสมด้วยถ้วยตวงที่แบ่งช่องแน่นอน แต่ถ้ารับค่าจาก sensor ซึ่งเป็นการวัดโดยประมาณ การสร้างเศษส่วนขนาดมหาศาลไม่ได้ทำให้ sensor แม่นขึ้น ใช้ float พร้อม tolerance ตามความคลาดเคลื่อนที่วัดได้จะเหมาะกว่า
limit_denominator(max_denominator) คืนเศษส่วนที่ใกล้ที่สุดภายใต้เพดานตัวส่วน จึงมีประโยชน์เมื่อต้องแปลง float เดิมให้เป็นอัตราส่วนอ่านง่าย แต่เป็น การประมาณ และอาจเลือกค่าอื่นเมื่อเพดานต่ำเกินไป ต้องบันทึกเพดานกับ error tolerance ไว้ในข้อกำหนดงาน อย่าใช้เพื่อแปลงเงินแบบเงียบ ๆ; งานเงินควรกำหนดหน่วยย่อยหรือ Decimal ตามกฎการปัดที่ธุรกิจกำหนด
Recap
- รับ decimal ที่ต้องตรงผ่าน string หรือ numerator/denominator
- อย่าเข้าใจว่า
Fraction(float)กู้ความตั้งใจเดิมได้ - จำกัดตัวส่วนเฉพาะเมื่อยอมรับการประมาณและทดสอบ tolerance แล้ว
- เลือก
float,FractionหรือDecimalให้ตรงชนิดของข้อมูลจริง