diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 0befb52910..0cb2f9223c 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -23,7 +23,7 @@ dps, convert_int_to_digit_list, machine_precision, machine_epsilon, get_precision, PrecisionValueError) from mathics.core.expression import ( - Integer, Real, Complex, Expression, Number, Symbol, from_python, + Integer, Real, Complex, Expression, Number, Symbol, Rational, from_python, MachineReal) from mathics.core.convert import from_sympy @@ -506,6 +506,125 @@ def apply(self, expr, k, evaluation): return Expression('Times', Integer(n), k) +class Rationalize(Builtin): + ''' +
+
'Rationalize[$x$]' +
converts a real number $x$ to a nearby rational number. +
'Rationalize[$x$, $dx$]' +
finds the rational number within $dx$ of $x$ with the smallest denominator. +
+ + >> Rationalize[2.2] + = 11 / 5 + + Not all numbers can be well approximated. + >> Rationalize[N[Pi]] + = 3.14159 + + Find the exact rational representation of 'N[Pi]' + >> Rationalize[N[Pi], 0] + = 245850922 / 78256779 + + #> Rationalize[1.6 + 0.8 I] + = 8 / 5 + 4 I / 5 + + #> Rationalize[N[Pi] + 0.8 I, 1*^-6] + = 355 / 113 + 4 I / 5 + + #> Rationalize[N[Pi] + 0.8 I, x] + : Tolerance specification x must be a non-negative number. + = Rationalize[3.14159 + 0.8 I, x] + + #> Rationalize[N[Pi] + 0.8 I, -1] + : Tolerance specification -1 must be a non-negative number. + = Rationalize[3.14159 + 0.8 I, -1] + + #> Rationalize[N[Pi] + 0.8 I, 0] + = 245850922 / 78256779 + 4 I / 5 + + #> Rationalize[17 / 7] + = 17 / 7 + + #> Rationalize[x] + = x + + #> Table[Rationalize[E, 0.1^n], {n, 1, 10}] + = {8 / 3, 19 / 7, 87 / 32, 193 / 71, 1071 / 394, 2721 / 1001, 15062 / 5541, 23225 / 8544, 49171 / 18089, 419314 / 154257} + + #> Rationalize[x, y] + : Tolerance specification y must be a non-negative number. + = Rationalize[x, y] + ''' + + messages = { + 'tolnn': 'Tolerance specification `1` must be a non-negative number.', + } + + rules = { + 'Rationalize[z_Complex]': 'Rationalize[Re[z]] + I Rationalize[Im[z]]', + 'Rationalize[z_Complex, dx_?Internal`RealValuedNumberQ]/;dx >= 0': 'Rationalize[Re[z], dx] + I Rationalize[Im[z], dx]', + } + + def apply(self, x, evaluation): + 'Rationalize[x_]' + + py_x = x.to_sympy() + if py_x is None or (not py_x.is_number) or (not py_x.is_real): + return x + return from_sympy(self.find_approximant(py_x)) + + @staticmethod + def find_approximant(x): + c = 1e-4 + it = sympy.ntheory.continued_fraction_convergents(sympy.ntheory.continued_fraction_iterator(x)) + for i in it: + p, q = i.as_numer_denom() + tol = c / q**2 + if abs(i - x) <= tol: + return i + if tol < machine_epsilon: + break + return x + + @staticmethod + def find_exact(x): + p, q = x.as_numer_denom() + it = sympy.ntheory.continued_fraction_convergents(sympy.ntheory.continued_fraction_iterator(x)) + for i in it: + p, q = i.as_numer_denom() + if abs(x - i) < machine_epsilon: + return i + + def apply_dx(self, x, dx, evaluation): + 'Rationalize[x_, dx_]' + py_x = x.to_sympy() + if py_x is None: + return x + py_dx = dx.to_sympy() + if py_dx is None or (not py_dx.is_number) or (not py_dx.is_real) or py_dx.is_negative: + return evaluation.message('Rationalize', 'tolnn', dx) + elif py_dx == 0: + return from_sympy(self.find_exact(py_x)) + a = self.approx_interval_continued_fraction(py_x - py_dx, py_x + py_dx) + sym_x = sympy.ntheory.continued_fraction_reduce(a) + return Rational(sym_x) + + @staticmethod + def approx_interval_continued_fraction(xmin, xmax): + result = [] + a_gen = sympy.ntheory.continued_fraction_iterator(xmin) + b_gen = sympy.ntheory.continued_fraction_iterator(xmax) + while True: + a, b = next(a_gen), next(b_gen) + if a == b: + result.append(a) + else: + result.append(min(a, b) + 1) + break + return result + + def chop(expr, delta=10.0 ** (-10.0)): if isinstance(expr, Real): if -delta < expr.get_float_value() < delta: diff --git a/mathics/core/numbers.py b/mathics/core/numbers.py index e3ff5a21a7..efcd51b862 100644 --- a/mathics/core/numbers.py +++ b/mathics/core/numbers.py @@ -17,6 +17,8 @@ # Number of bits of machine precision machine_precision = 53 + + machine_epsilon = 2 ** (1 - machine_precision)