diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py
index 946d2c2af..34164c5f7 100644
--- a/mathics/builtin/arithmetic.py
+++ b/mathics/builtin/arithmetic.py
@@ -6,6 +6,7 @@
Basic arithmetic functions, including complex number arithmetic.
"""
+from typing import Callable, Optional
from mathics.eval.numerify import numerify
@@ -52,7 +53,12 @@
from mathics.core.element import ElementsProperties
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
-from mathics.core.number import SpecialValueError, dps, min_prec
+from mathics.core.number import (
+ FP_MANTISA_BINARY_DIGITS,
+ SpecialValueError,
+ dps,
+ min_prec,
+)
from mathics.core.symbols import (
Atom,
Symbol,
@@ -68,9 +74,7 @@
SymbolAnd,
SymbolComplexInfinity,
SymbolDirectedInfinity,
- SymbolIndeterminate,
SymbolInfix,
- SymbolOverflow,
SymbolPiecewise,
SymbolPossibleZeroQ,
SymbolTable,
@@ -79,20 +83,35 @@
from mathics.eval.nevaluator import eval_N
-@lru_cache(maxsize=4096)
-def call_mpmath(mpmath_function, mpmath_args):
- try:
- return mpmath_function(*mpmath_args)
- except ValueError as exc:
- text = str(exc)
- if text == "gamma function pole":
- return SymbolComplexInfinity
- else:
- raise
- except ZeroDivisionError:
- return
- except SpecialValueError as exc:
- return Symbol(exc.name)
+# @lru_cache(maxsize=4096)
+def call_mpmath(
+ mpmath_function: Callable, mpmath_args: tuple, prec: Optional[int] = None
+):
+ """
+ calls the mpmath_function with mpmath_args parms
+ if prec=None, use floating point arithmetic.
+ Otherwise, work with prec bits of precision.
+ """
+ # TODO: rocky, please help me with the annotations
+ # in the signature of this function.
+ if prec is None:
+ prec = FP_MANTISA_BINARY_DIGITS
+ with mpmath.workprec(prec):
+ try:
+ result_mp = mpmath_function(*mpmath_args)
+ if prec != FP_MANTISA_BINARY_DIGITS:
+ return from_mpmath(result_mp, prec)
+ return from_mpmath(result_mp)
+ except ValueError as exc:
+ text = str(exc)
+ if text == "gamma function pole":
+ return SymbolComplexInfinity
+ else:
+ raise
+ except ZeroDivisionError:
+ return
+ except SpecialValueError as exc:
+ return Symbol(exc.name)
class _MPMathFunction(SympyFunction):
@@ -147,38 +166,18 @@ def eval(self, z, evaluation):
return
result = call_mpmath(mpmath_function, tuple(float_args))
-
- if isinstance(result, (mpmath.mpc, mpmath.mpf)):
- if mpmath.isinf(result) and isinstance(result, mpmath.mpc):
- result = SymbolComplexInfinity
- elif mpmath.isinf(result) and result > 0:
- result = Expression(SymbolDirectedInfinity, Integer1)
- elif mpmath.isinf(result) and result < 0:
- result = Expression(SymbolDirectedInfinity, IntegerM1)
- elif mpmath.isnan(result):
- result = SymbolIndeterminate
- else:
- # FIXME: replace try/except as a context manager
- # like "with evaluation.from_mpmath()...
- # which can be instrumented for
- # or mpmath tracing and benchmarking on demand.
- # Then use it on other places where mpmath appears.
- try:
- result = from_mpmath(result)
- except OverflowError:
- evaluation.message("General", "ovfl")
- result = Expression(SymbolOverflow)
else:
prec = min_prec(*args)
d = dps(prec)
args = [eval_N(arg, evaluation, Integer(d)) for arg in args]
+
with mpmath.workprec(prec):
+ # to_mpmath seems to require that the precision is set from outside
mpmath_args = [x.to_mpmath() for x in args]
if None in mpmath_args:
return
- result = call_mpmath(mpmath_function, tuple(mpmath_args))
- if isinstance(result, (mpmath.mpc, mpmath.mpf)):
- result = from_mpmath(result, precision=prec)
+
+ result = call_mpmath(mpmath_function, tuple(mpmath_args), prec)
return result
@@ -723,7 +722,7 @@ def to_sympy(self, expr, **kwargs):
return sympy.zoo
-class I(Predefined):
+class I_(Predefined):
"""
:Imaginary unit:https://en.wikipedia.org/wiki/Imaginary_unit \
(:WMA:https://reference.wolfram.com/language/ref/I.html)
@@ -739,6 +738,7 @@ class I(Predefined):
= 10
"""
+ name = "I"
summary_text = "imaginary unit"
python_equivalent = 1j
diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py
index a8faa667f..73e2cbcbb 100644
--- a/mathics/builtin/numbers/constants.py
+++ b/mathics/builtin/numbers/constants.py
@@ -27,6 +27,7 @@
MIN_MACHINE_NUMBER,
PrecisionValueError,
get_precision,
+ prec,
)
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolIndeterminate
@@ -43,8 +44,11 @@ def mp_constant(fn: str, d=None) -> mpmath.mpf:
# ask for a certain number of digits, but the
# accuracy will be less than that. Figure out
# what's up and compensate somehow.
- mpmath.mp.dps = int_d = int(d * 3.321928)
- return getattr(mpmath, fn)(prec=int_d)
+
+ int_d = prec(d)
+ with mpmath.workprec(int_d):
+ result = str(getattr(mpmath, fn)(prec=int_d))
+ return result
def mp_convert_constant(obj, **kwargs):
diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py
index 76c016de7..340d26e1f 100644
--- a/mathics/builtin/specialfns/gamma.py
+++ b/mathics/builtin/specialfns/gamma.py
@@ -112,17 +112,6 @@ def eval_with_z(self, z, a, b, evaluation):
return
result = call_mpmath(mpmath_function, tuple(float_args))
- if isinstance(result, (mpmath.mpc, mpmath.mpf)):
- if mpmath.isinf(result) and isinstance(result, mpmath.mpc):
- result = SymbolComplexInfinity
- elif mpmath.isinf(result) and result > 0:
- result = Expression(SymbolDirectedInfinity, Integer1)
- elif mpmath.isinf(result) and result < 0:
- result = Expression(SymbolDirectedInfinity, Integer(-1))
- elif mpmath.isnan(result):
- result = SymbolIndeterminate
- else:
- result = from_mpmath(result)
else:
prec = min_prec(*args)
d = dps(prec)
@@ -131,9 +120,7 @@ def eval_with_z(self, z, a, b, evaluation):
mpmath_args = [x.to_mpmath() for x in args]
if None in mpmath_args:
return
- result = call_mpmath(mpmath_function, tuple(mpmath_args))
- if isinstance(result, (mpmath.mpc, mpmath.mpf)):
- result = from_mpmath(result, precision=prec)
+ result = call_mpmath(mpmath_function, tuple(mpmath_args), prec)
return result
diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py
index 68f0e8542..7f09fcde9 100644
--- a/mathics/core/atoms.py
+++ b/mathics/core/atoms.py
@@ -249,13 +249,18 @@ def to_sympy(self, **kwargs):
def to_python(self, *args, **kwargs):
return self.value
- def round(self, d=None) -> Union["MachineReal", "PrecisionReal"]:
+ def round(self, d: Optional[int] = None) -> Union["MachineReal", "PrecisionReal"]:
+ """
+ Produce a Real approximation of ``self`` with decimal precision ``d``.
+ If ``d`` is ``None``, and self.value fits in a float,
+ returns a ``MachineReal`` number.
+ Is the low-level equivalent to ``N[self, d]``.
+ """
if d is None:
d = self.value.bit_length()
if d <= FP_MANTISA_BINARY_DIGITS:
return MachineReal(float(self.value))
else:
- # FP_MANTISA_BINARY_DIGITS / log_2(10) + 1
d = MACHINE_PRECISION_VALUE
return PrecisionReal(sympy.Float(self.value, d))
@@ -441,7 +446,10 @@ def make_boxes(self, form):
def is_zero(self) -> bool:
return self.value == 0.0
- def round(self, d=None) -> "MachineReal":
+ def round(self, d: Optional[int] = None) -> "MachineReal":
+ """
+ Produce a Real approximation of ``self`` with decimal precision ``d``.
+ """
return self
def sameQ(self, other) -> bool:
@@ -540,12 +548,11 @@ def make_boxes(self, form):
self, dps(self.get_precision()), None, None, _number_form_options
)
- def round(self, d=None) -> Union[MachineReal, "PrecisionReal"]:
+ def round(self, d: Optional[int] = None) -> Union[MachineReal, "PrecisionReal"]:
if d is None:
return MachineReal(float(self.value))
- else:
- d = min(dps(self.get_precision()), d)
- return PrecisionReal(self.value.n(d))
+ _prec = min(prec(d), self.value._prec)
+ return PrecisionReal(sympy.Float(self.value, precision=_prec))
def sameQ(self, other) -> bool:
"""Mathics SameQ for PrecisionReal"""
diff --git a/mathics/core/convert/mpmath.py b/mathics/core/convert/mpmath.py
index f6e2a885e..2b122eebb 100644
--- a/mathics/core/convert/mpmath.py
+++ b/mathics/core/convert/mpmath.py
@@ -6,8 +6,17 @@
import mpmath
import sympy
-from mathics.core.atoms import Complex, MachineReal, MachineReal0, PrecisionReal
+from mathics.core.atoms import (
+ Complex,
+ Integer1,
+ IntegerM1,
+ MachineReal,
+ MachineReal0,
+ PrecisionReal,
+)
+from mathics.core.expression import Expression
from mathics.core.symbols import Atom
+from mathics.core.systemsymbols import SymbolDirectedInfinity
@lru_cache(maxsize=1024)
diff --git a/mathics/eval/nevaluator.py b/mathics/eval/nevaluator.py
index e75bf54da..079205433 100644
--- a/mathics/eval/nevaluator.py
+++ b/mathics/eval/nevaluator.py
@@ -13,13 +13,13 @@
import sympy
-from mathics.core.atoms import Number
+from mathics.core.atoms import Number, PrecisionReal
from mathics.core.attributes import A_N_HOLD_ALL, A_N_HOLD_FIRST, A_N_HOLD_REST
from mathics.core.convert.sympy import from_sympy
from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.number import PrecisionValueError, get_precision
+from mathics.core.number import PrecisionValueError, dps, get_precision
from mathics.core.symbols import Atom
from mathics.core.systemsymbols import SymbolMachinePrecision, SymbolN
@@ -50,7 +50,6 @@ def eval_NValues(
stored in ``evaluation.definitions``.
If ``prec`` can not be evaluated as a number, returns None, otherwise, returns an expression.
"""
-
# The first step is to determine the precision goal
try:
# Here ``get_precision`` is called with ``show_messages``
diff --git a/test/builtin/atomic/test_numbers.py b/test/builtin/atomic/test_numbers.py
index ebc15da91..da4d17a16 100644
--- a/test/builtin/atomic/test_numbers.py
+++ b/test/builtin/atomic/test_numbers.py
@@ -270,3 +270,38 @@ def test_accuracy(str_expr, str_expected):
)
def test_precision(str_expr, str_expected):
check_evaluation(f"Precision[{str_expr}]", str_expected)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msg"),
+ [
+ (None, None, None),
+ ("N[Sqrt[2], 41]//Precision", "41.", "first round sqrt[2`41]"),
+ ("N[Sqrt[2], 40]//Precision", "40.", "first round sqrt[2`40]"),
+ ("N[Sqrt[2], 41]//Precision", "41.", "second round sqrt[2`41]"),
+ ("N[Sqrt[2], 40]//Precision", "40.", "second round sqrt[2`40]"),
+ (
+ "N[Sqrt[2], 41]",
+ '"1.4142135623730950488016887242096980785697"',
+ "third round sqrt[2`41]",
+ ),
+ (
+ "Precision/@Table[N[Pi,p],{p, {5, 100, MachinePrecision, 20}}]",
+ "{5., 100., MachinePrecision, 20.}",
+ None,
+ ),
+ (
+ "Precision/@Table[N[Sin[1],p],{p, {5, 100, MachinePrecision, 20}}]",
+ "{5., 100., MachinePrecision, 20.}",
+ None,
+ ),
+ ("N[Sqrt[2], 40]", '"1.414213562373095048801688724209698078570"', None),
+ ("N[Sqrt[2], 4]", '"1.414"', None),
+ ("N[Pi, 40]", '"3.141592653589793238462643383279502884197"', None),
+ ("N[Pi, 4]", '"3.142"', None),
+ ("N[Pi, 41]", '"3.1415926535897932384626433832795028841972"', None),
+ ("N[Sqrt[2], 41]", '"1.4142135623730950488016887242096980785697"', None),
+ ],
+)
+def test_change_prec(str_expr, str_expected, msg):
+ check_evaluation(str_expr, str_expected, failure_message=msg)