งูต้นฉบับกำลังวางชิ้นส่วนสองขนาดบนตาชั่งให้ได้อัตราส่วนพอดี

Python Fraction: เก็บอัตราส่วนให้ตรงตั้งแต่รับค่า

พี่โชว์Engineering

สูตรอาหารหรือการแบ่งงบที่เริ่มจาก 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 ให้ตรงชนิดของข้อมูลจริง