]> granicus.if.org Git - python/commitdiff
Add first-cut at an approximation function (still needs rounding tweaks). Add contin...
authorRaymond Hettinger <python@rcn.com>
Thu, 24 Jan 2008 00:54:21 +0000 (00:54 +0000)
committerRaymond Hettinger <python@rcn.com>
Thu, 24 Jan 2008 00:54:21 +0000 (00:54 +0000)
Lib/rational.py
Lib/test/test_rational.py

index 19e7f141b484bcd84ddbf13649c42f3e953cb69f..5d21a8f48748bd696e792469cc1ffda65f1b7709 100755 (executable)
@@ -172,6 +172,42 @@ class Rational(RationalAbc):
         else:
             return cls(digits, 10 ** -exp)
 
+    @classmethod
+    def from_continued_fraction(cls, seq):
+        'Build a Rational from a continued fraction expessed as a sequence'
+        n, d = 1, 0
+        for e in reversed(seq):
+            n, d = d, n
+            n += e * d
+        return cls(n, d)
+
+    def as_continued_fraction(self):
+        'Return continued fraction expressed as a list'
+        n = self.numerator
+        d = self.denominator
+        cf = []
+        while d:
+            e = int(n // d)
+            cf.append(e)
+            n -= e * d
+            n, d = d, n
+        return cf
+
+    @classmethod
+    def approximate_from_float(cls, f, max_denominator):
+        'Best rational approximation to f with a denominator <= max_denominator'
+        # XXX First cut at algorithm
+        # Still needs rounding rules as specified at
+        #       http://en.wikipedia.org/wiki/Continued_fraction
+        cf = cls.from_float(f).as_continued_fraction()
+        result = new = Rational(0, 1)
+        for i in range(1, len(cf)):
+            new = cls.from_continued_fraction(cf[:i])
+            if new.denominator > max_denominator:
+                break
+            result = new
+        return result
+
     @property
     def numerator(a):
         return a._numerator
index 5ee7b7d2866fc41ebe0d976ee545e0b8b51df037..76757ba03bd526f569fb3a08a4e49bb79b99e4bc 100644 (file)
@@ -135,6 +135,18 @@ class RationalTest(unittest.TestCase):
             TypeError, "Cannot convert sNaN to Rational.",
             R.from_decimal, Decimal("snan"))
 
+    def testFromContinuedFraction(self):
+        self.assertRaises(TypeError, R.from_continued_fraction, None)
+        phi = R.from_continued_fraction([1]*100)
+        self.assertEquals(round(phi - (1 + 5 ** 0.5) / 2, 10), 0.0)
+
+    def testAsContinuedFraction(self):
+        self.assertEqual(R.from_float(math.pi).as_continued_fraction()[:15],
+                         [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 3, 3])
+
+    def testApproximateFromFloat(self):
+        self.assertEqual(R.approximate_from_float(math.pi, 10000), R(355, 113))
+
     def testConversions(self):
         self.assertTypedEquals(-1, trunc(R(-11, 10)))
         self.assertTypedEquals(-2, R(-11, 10).__floor__())