diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index d73efa8ee..e1a5246ed 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -17,10 +17,7 @@ However, things like 'N[Pi, 100]' should work as expected. """ -from functools import lru_cache - import mpmath -import sympy from mathics.core.atoms import ( Integer, @@ -33,6 +30,7 @@ from mathics.core.attributes import A_LISTABLE, A_PROTECTED, A_READ_PROTECTED from mathics.core.builtin import Builtin, Predefined from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import ( @@ -52,100 +50,19 @@ SymbolRound, ) from mathics.eval.nevaluator import eval_N -from mathics.eval.numbers.numbers import eval_Accuracy, eval_Precision +from mathics.eval.numbers.numbers import ( + check_finite_decimal, + convert_float_base, + convert_repeating_decimal, + eval_Accuracy, + eval_Precision, + log_n_b, +) SymbolIntegerDigits = Symbol("IntegerDigits") SymbolIntegerExponent = Symbol("IntegerExponent") -@lru_cache() -def log_n_b(py_n, py_b) -> int: - return ( - int(mpmath.floor(mpmath.log(py_n, py_b))) + 1 if py_n != 0 and py_n != 1 else 1 - ) - - -def check_finite_decimal(denominator): - # The rational number is finite decimal if the denominator has form 2^a * 5^b - while denominator % 5 == 0: - denominator = denominator / 5 - - while denominator % 2 == 0: - denominator = denominator / 2 - - return True if denominator == 1 else False - - -def convert_repeating_decimal(numerator, denominator, base): - head = [x for x in str(numerator // denominator)] - tails = [] - subresults = [numerator % denominator] - numerator %= denominator - - while numerator != 0: # only rational input can go to this case - numerator *= base - result_digit, numerator = divmod(numerator, denominator) - tails.append(str(result_digit)) - if numerator not in subresults: - subresults.append(numerator) - else: - break - - for i in range(len(head) - 1, -1, -1): - j = len(tails) - 1 - if head[i] != tails[j]: - break - else: - del tails[j] - tails.insert(0, head[i]) - del head[i] - - # truncate all leading 0's - if all(elem == "0" for elem in head): - for i in range(0, len(tails)): - if tails[0] == "0": - tails = tails[1:] + [str(0)] - else: - break - return (head, tails) - - -def convert_float_base(x, base, precision=10): - length_of_int = 0 if x == 0 else int(mpmath.log(x, base)) - # iexps = list(range(length_of_int, -1, -1)) - - def convert_int(x, base, exponents): - out = [] - for e in range(0, exponents + 1): - d = x % base - out.append(d) - x = x / base - if x == 0: - break - out.reverse() - return out - - def convert_float(x, base, exponents): - out = [] - for e in range(0, exponents): - d = int(x * base) - out.append(d) - x = (x * base) - d - if x == 0: - break - return out - - int_part = convert_int(int(x), base, length_of_int) - if isinstance(x, (float, sympy.Float)): - # fexps = list(range(-1, -int(precision + 1), -1)) - real_part = convert_float(x - int(x), base, precision + 1) - return int_part + real_part - elif isinstance(x, int): - return int_part - else: - raise TypeError(x) - - class Accuracy(Builtin): """ @@ -255,7 +172,7 @@ class IntegerExponent(Builtin): summary_text = "number of trailing 0s in a given base" - def eval_two_arg_integers(self, n: Integer, b: Integer, evaluation): + def eval_two_arg_integers(self, n: Integer, b: Integer, evaluation: Evaluation): """IntegerExponent[n_Integer, b_Integer]""" py_n, py_b = n.value, b.value diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py index a6243314f..da392205f 100644 --- a/mathics/builtin/box/expression.py +++ b/mathics/builtin/box/expression.py @@ -1,6 +1,3 @@ -# This is never intended to go in Mathics3 docs -no_doc = True - from typing import Optional, Sequence, Union from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED @@ -10,6 +7,9 @@ from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolHoldForm, ensure_context +# This is never intended to go in Mathics3 docs +no_doc = True + def split_name(name: str) -> str: """ diff --git a/mathics/eval/numbers/numbers.py b/mathics/eval/numbers/numbers.py index fe79ec447..52821e044 100644 --- a/mathics/eval/numbers/numbers.py +++ b/mathics/eval/numbers/numbers.py @@ -3,6 +3,7 @@ Implementation of numbers handling functions. """ +from functools import lru_cache from typing import Optional import mpmath @@ -20,6 +21,87 @@ from mathics.core.symbols import SymbolPlus +def check_finite_decimal(denominator): + # The rational number is finite decimal if the denominator has form 2^a * 5^b + while denominator % 5 == 0: + denominator = denominator / 5 + + while denominator % 2 == 0: + denominator = denominator / 2 + + return True if denominator == 1 else False + + +def convert_repeating_decimal(numerator, denominator, base): + head = [x for x in str(numerator // denominator)] + tails = [] + subresults = [numerator % denominator] + numerator %= denominator + + while numerator != 0: # only rational input can go to this case + numerator *= base + result_digit, numerator = divmod(numerator, denominator) + tails.append(str(result_digit)) + if numerator not in subresults: + subresults.append(numerator) + else: + break + + for i in range(len(head) - 1, -1, -1): + j = len(tails) - 1 + if head[i] != tails[j]: + break + else: + del tails[j] + tails.insert(0, head[i]) + del head[i] + + # truncate all leading 0's + if all(elem == "0" for elem in head): + for i in range(0, len(tails)): + if tails[0] == "0": + tails = tails[1:] + [str(0)] + else: + break + return (head, tails) + + +def convert_float_base(x, base, precision=10): + length_of_int = 0 if x == 0 else int(mpmath.log(x, base)) + # iexps = list(range(length_of_int, -1, -1)) + + def convert_int(x, base, exponents): + out = [] + for e in range(0, exponents + 1): + d = x % base + out.append(d) + x = x / base + if x == 0: + break + out.reverse() + return out + + def convert_float(x, base, exponents): + out = [] + for e in range(0, exponents): + d = int(x * base) + out.append(d) + x = (x * base) - d + if x == 0: + break + return out + + int_part = convert_int(int(x), base, length_of_int) + if isinstance(x, (float, sympy.Float)): + # fexps = list(range(-1, -int(precision + 1), -1)) + real_part = convert_float(x - int(x), base, precision + 1) + return int_part + real_part + elif isinstance(x, int): + return int_part + else: + raise TypeError(x) + + def eval_Accuracy(z: BaseElement) -> Optional[float]: """ Determine the accuracy of an expression expr. @@ -129,7 +211,7 @@ def cancel(expr): return None # result = sympy.powsimp(result, deep=True) - result = sympy.cancel(result) + result = tracing.run_sympy(sympy.cancel, result) # cancel factors out rationals, so we factor them again result = sympy_factor(result) @@ -140,25 +222,11 @@ def cancel(expr): return expr -def cancel(expr): - if expr.has_form("Plus", None): - return Expression(SymbolPlus, *[cancel(element) for element in expr.elements]) - else: - try: - result = expr.to_sympy() - if result is None: - return None - - # result = sympy.powsimp(result, deep=True) - result = tracing.run_sympy(sympy.cancel, result) - - # cancel factors out rationals, so we factor them again - result = sympy_factor(result) - - return from_sympy(result) - except sympy.PolynomialError: - # e.g. for non-commutative expressions - return expr +@lru_cache() +def log_n_b(py_n, py_b) -> int: + return ( + int(mpmath.floor(mpmath.log(py_n, py_b))) + 1 if py_n != 0 and py_n != 1 else 1 + ) def sympy_factor(expr_sympy):