rationals.toRational now uses an algorithm based on continued fractions; refs #4968

This commit is contained in:
Andreas Rumpf 2017-10-30 08:54:43 +01:00
commit 90c1b94fbe
2 changed files with 36 additions and 45 deletions

View file

@ -31,3 +31,7 @@
- ``mod`` and bitwise ``and`` do not produce ``range`` subtypes anymore. This - ``mod`` and bitwise ``and`` do not produce ``range`` subtypes anymore. This
turned out to be more harmful than helpful and the language is simpler turned out to be more harmful than helpful and the language is simpler
without this special typing rule. without this special typing rule.
- Added ``algorithm.rotateLeft``.
- ``rationals.toRational`` now uses an algorithm based on continued fractions.
This means its results are more precise and it can run into an infinite loop
anymore.

View file

@ -39,47 +39,13 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] =
result.num = x result.num = x
result.den = 1 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] = proc toRational*(x: float, n: int = high(int)): Rational[int] =
## Calculate the best rational numerator and denominator ## Calculates the best rational numerator and denominator
## that approximates to `x`, where the denominator is ## that approximates to `x`, where the denominator is
## smaller than `n` (default is the largest possible ## 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 ## The algorithm is based on the theory of continued fractions.
## after John Farey
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## import math, rationals ## import math, rationals
@ -88,13 +54,26 @@ proc toRational*(x: float, n: int = high(int)): Rational[int] =
## let x = toRational(PI, t) ## let x = toRational(PI, t)
## let newPI = x.num / x.den ## let newPI = x.num / x.den
## echo x, " ", newPI, " error: ", PI - newPI, " ", t ## echo x, " ", newPI, " error: ", PI - newPI, " ", t
if x > 1:
result = toRationalSub(1.0/x, n) # David Eppstein / UC Irvine / 8 Aug 1993
swap(result.num, result.den) # With corrections from Arno Formella, May 2008
elif x == 1.0: var
result = 1 // 1 m11, m22 = 1
else: m12, m21 = 0
result = toRationalSub(x, n) ai = x.int
x = x
while m21.float * ai.float + m22.float <= n.float:
swap m12, m11
swap m22, m21
m11 = m12 * ai + m11
m21 = m22 * ai + m21
if x == ai.float: # division by zero
break
if x > 0x7FFFFFFF.float: # representation failure
break
x = 1.0 / (x - ai.float)
ai = x.int
result = m11 // m21
proc toFloat*[T](x: Rational[T]): float = proc toFloat*[T](x: Rational[T]): float =
## Convert a rational number `x` to a float. ## Convert a rational number `x` to a float.
@ -346,7 +325,15 @@ when isMainModule:
assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7 assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7
assert toInt(z) == 0 assert toInt(z) == 0
assert toRational(0.98765432) == 12345679 // 12500000 assert toRational(0.98765432) == 5376864444397469455 // 5444075255396513284
assert toRational(PI) == 8566508067901016491 // 2726804208086097199
assert toRational(0.1, 1000000) == 1 // 10 assert toRational(0.1, 1000000) == 1 // 10
assert toRational(0.9, 1000000) == 9 // 10 assert toRational(0.9, 1000000) == 9 // 10
#assert toRational(PI) == 80143857 // 25510582 #assert toRational(PI) == 80143857 // 25510582
assert toRational(0.0) == 0 // 1
assert toRational(-0.25, 10) == 1 // -4
assert toRational(3.2, 10) == 16 // 5
assert toRational(0.33, 100) == 33 // 100
assert toRational(0.22, 50) == 11 // 50
assert toRational(10.0) == 10 // 1