rationals.toRational now uses an algorithm based on continued fractions; refs #4968
This commit is contained in:
parent
31ef5b6eb9
commit
90c1b94fbe
2 changed files with 36 additions and 45 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue