summary refs log tree commit diff stats
path: root/lib/pure/rationals.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/rationals.nim')
-rw-r--r--lib/pure/rationals.nim82
1 files changed, 33 insertions, 49 deletions
diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim
index c2ba2b1f3..7fb24c26f 100644
--- a/lib/pure/rationals.nim
+++ b/lib/pure/rationals.nim
@@ -39,47 +39,13 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] =
   result.num = x
   result.den = 1
 
-proc toRationalSub(x: float, n: int): Rational[int] =
-  var
-    a = 0'i64
-    b, c, d = 1'i64
-  result = 0 // 1   # rational 0
-  while b <= n and d <= n:
-    let ac = (a+c)
-    let bd = (b+d)
-    # scale by 1000 so not overflow for high precision
-    let mediant = (ac.float/1000) / (bd.float/1000)
-    if x == mediant:
-      if bd <= n:
-        result.num = ac.int
-        result.den = bd.int
-        return result
-      elif d > b:
-        result.num = c.int
-        result.den = d.int
-        return result
-      else:
-        result.num = a.int
-        result.den = b.int
-        return result
-    elif x > mediant:
-      a = ac
-      b = bd
-    else:
-      c = ac
-      d = bd
-  if (b > n):
-    return initRational(c.int, d.int)
-  return initRational(a.int, b.int)
-
-proc toRational*(x: float, n: int = high(int)): Rational[int] =
-  ## Calculate the best rational numerator and denominator
+proc toRational*(x: float, n: int = high(int32)): Rational[int] =
+  ## Calculates the best rational numerator and denominator
   ## that approximates to `x`, where the denominator is
   ## smaller than `n` (default is the largest possible
-  ## int to give maximum resolution)
+  ## int to give maximum resolution).
   ##
-  ## The algorithm is based on the Farey sequence named
-  ## after John Farey
+  ## The algorithm is based on the theory of continued fractions.
   ##
   ## .. code-block:: Nim
   ##  import math, rationals
@@ -88,13 +54,24 @@ proc toRational*(x: float, n: int = high(int)): Rational[int] =
   ##    let x = toRational(PI, t)
   ##    let newPI = x.num / x.den
   ##    echo x, " ", newPI, " error: ", PI - newPI, "  ", t
-  if x > 1:
-    result = toRationalSub(1.0/x, n)
-    swap(result.num, result.den)
-  elif x == 1.0:
-    result = 1 // 1
-  else:
-    result = toRationalSub(x, n)
+
+  # David Eppstein / UC Irvine / 8 Aug 1993
+  # With corrections from Arno Formella, May 2008
+  var
+    m11, m22 = 1
+    m12, m21 = 0
+    ai = int(x)
+    x = x
+  while m21 * ai + m22 <= n:
+    swap m12, m11
+    swap m22, m21
+    m11 = m12 * ai + m11
+    m21 = m22 * ai + m21
+    if x == float(ai): break  # division by zero
+    x = 1/(x - float(ai))
+    if x > float(high(int32)): break  # representation failure
+    ai = int(x)
+  result = m11 // m21
 
 proc toFloat*[T](x: Rational[T]): float =
   ## Convert a rational number `x` to a float.
@@ -346,7 +323,14 @@ when isMainModule:
   assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7
   assert toInt(z) == 0
 
-  assert toRational(0.98765432) == 12345679 // 12500000
-  assert toRational(0.1, 1000000) == 1 // 10
-  assert toRational(0.9, 1000000) == 9 // 10
-  #assert toRational(PI) == 80143857 // 25510582
+  assert toRational(0.98765432) == 2111111029 // 2137499919
+  assert toRational(PI) == 817696623 // 260280919
+  assert toRational(0.1) == 1 // 10
+  assert toRational(0.9) == 9 // 10
+
+  assert toRational(0.0) == 0 // 1
+  assert toRational(-0.25) == 1 // -4
+  assert toRational(3.2) == 16 // 5
+  assert toRational(0.33) == 33 // 100
+  assert toRational(0.22) == 11 // 50
+  assert toRational(10.0) == 10 // 1