Repository: sympy/sympy

Issue:
N(<decimal string>) limited to double precision
Is input like `N('1.23')` supposed to work?  It is limited to double precision.

````
In [18]: s = str(pi.evalf(1024))  # very accurate pi string

In [19]: a = Float(s, 128)

In [20]: sin(a)   # looks good
Out[20]: -3.3218e-130

In [21]: 

In [21]: b = N(s, 128)

In [22]: sin(b)  # not good
Out[22]: 0.00000000000000012246467991473531772260659322749979970830539012997919494882577162608696099732581037750932552756901365545642854007441419
````

Let's look at the `N` code:
````
return sympify(x).evalf(n, **options
````
So this happens because `N` runs `sympify(str)` which has no knowledge of `n`.

It should be easy enough to special-case strings that contain a decimal point (and only digits).  We could call Float directly in those cases.

Even so, some cases that would still lose precision: `N('1.0*x')`, `N('1.0/3.0',128)` but meh, diminishing returns at some point!

Discussion:
cbm755: I'm fixing it in Float, because when I tried to fix it in `sympify` it got messy.

https://github.com/cbm755/sympy/tree/sympify_hiprec_str

Plus, I think Float should work like this too!
smichr: So if I understand, you are using a string 'min15' so we can differentiate between the case of a desired 15 and the default "15 or higher if it's a string" case. Why not just set the default to None so the user has to read the docstring to know what the default is -- which is probably what they will need to do to understand 'min15', anyway? (I think 'min15' is a sane choice but using None is more pythonic.)
cbm755: Thank you @smichr that is nicer, done as you describe.

Also, fixed up documentation.

Finally, fixed the failing test.  So in total, this changes two existing tests (long int in test_numbers.py is an improvement, nfloat perhaps less obvious: my test change just maintains the status quo there).

Patch:
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index 5b57771f13..d80101460d 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -456,9 +456,7 @@ def cofactors(self, other):
 
 
 class Float(Number):
-    """
-    Represents a floating point number. It is capable of representing
-    arbitrary-precision floating-point numbers.
+    """Represent a floating-point number of arbitrary precision.
 
     Examples
     ========
@@ -469,16 +467,38 @@ class Float(Number):
     >>> Float(3)
     3.00000000000000
 
-    Floats can be created from a string representations of Python floats
-    to force ints to Float or to enter high-precision (> 15 significant
-    digits) values:
+    Creating Floats from strings (and Python ``int`` and ``long``
+    types) will give a minimum precision of 15 digits, but the
+    precision will automatically increase to capture all digits
+    entered.
+
+    >>> Float(1)
+    1.00000000000000
+    >>> Float(10**20)
+    100000000000000000000.
+    >>> Float('1e20')
+    100000000000000000000.
+
+    However, *floating-point* numbers (Python ``float`` types) retain
+    only 15 digits of precision:
+
+    >>> Float(1e20)
+    1.00000000000000e+20
+    >>> Float(1.23456789123456789)
+    1.23456789123457
+
+    It may be preferable to enter high-precision decimal numbers
+    as strings:
+
+    Float('1.23456789123456789')
+    1.23456789123456789
+
+    The desired number of digits can also be specified:
 
-    >>> Float('.0010')
-    0.00100000000000000
-    >>> Float('1e-3')
-    0.00100000000000000
     >>> Float('1e-3', 3)
     0.00100
+    >>> Float(100, 4)
+    100.0
 
     Float can automatically count significant figures if a null string
     is sent for the precision; space are also allowed in the string. (Auto-
@@ -602,7 +622,7 @@ class Float(Number):
 
     is_Float = True
 
-    def __new__(cls, num, prec=15):
+    def __new__(cls, num, prec=None):
         if isinstance(num, string_types):
             num = num.replace(' ', '')
             if num.startswith('.') and len(num) > 1:
@@ -616,7 +636,20 @@ def __new__(cls, num, prec=15):
         elif isinstance(num, mpmath.mpf):
             num = num._mpf_
 
-        if prec == '':
+        if prec is None:
+            dps = 15
+            if isinstance(num, string_types) and _literal_float(num):
+                try:
+                    Num = decimal.Decimal(num)
+                except decimal.InvalidOperation:
+                    pass
+                else:
+                    isint = '.' not in num
+                    num, dps = _decimal_to_Rational_prec(Num)
+                    if num.is_Integer and isint:
+                        dps = max(dps, len(str(num).lstrip('-')))
+                    dps = max(15, dps)
+        elif prec == '':
             if not isinstance(num, string_types):
                 raise ValueError('The null string can only be used when '
                 'the number to Float is passed as a string or an integer.')


Test patch:
diff --git a/sympy/core/tests/test_evalf.py b/sympy/core/tests/test_evalf.py
index a09c932276..e5d222bb24 100644
--- a/sympy/core/tests/test_evalf.py
+++ b/sympy/core/tests/test_evalf.py
@@ -1,7 +1,7 @@
-from sympy import (Add, ceiling, cos, E, Eq, exp, factorial, fibonacci, floor,
-                   Function, GoldenRatio, I, log, Mul, oo, pi, Pow, Rational,
-                   sin, sqrt, sstr, sympify, S, integrate, atan, product,
-                   Sum, Product, Integral)
+from sympy import (Abs, Add, atan, ceiling, cos, E, Eq, exp, factorial,
+                   fibonacci, floor, Function, GoldenRatio, I, Integral,
+                   integrate, log, Mul, N, oo, pi, Pow, product, Product,
+                   Rational, S, Sum, sin, sqrt, sstr, sympify)
 from sympy.core.evalf import complex_accuracy, PrecisionExhausted, scaled_zero
 from sympy.core.compatibility import long
 from mpmath import inf, ninf
@@ -447,3 +447,11 @@ def test_evalf_integral():
     # test that workprec has to increase in order to get a result other than 0
     eps = Rational(1, 1000000)
     assert Integral(sin(x), (x, -pi, pi + eps)).n(2)._prec == 10
+
+
+def test_issue_8821_highprec_from_str():
+    s = str(pi.evalf(128))
+    p = N(s)
+    assert Abs(sin(p)) < 1e-15
+    p = N(s, 64)
+    assert Abs(sin(p)) < 1e-64
diff --git a/sympy/core/tests/test_function.py b/sympy/core/tests/test_function.py
index c2e0a18695..8701480bc7 100644
--- a/sympy/core/tests/test_function.py
+++ b/sympy/core/tests/test_function.py
@@ -603,10 +603,11 @@ def test_nfloat():
     eq = x**(S(4)/3) + 4*x**(x/3)/3
     assert _aresame(nfloat(eq), x**(S(4)/3) + (4.0/3)*x**(x/3))
     big = 12345678901234567890
-    Float_big = Float(big)
-    assert _aresame(nfloat(x**big, exponent=True),
-                    x**Float_big)
+    # specify precision to match value used in nfloat
+    Float_big = Float(big, 15)
     assert _aresame(nfloat(big), Float_big)
+    assert _aresame(nfloat(big*x), Float_big*x)
+    assert _aresame(nfloat(x**big, exponent=True), x**Float_big)
     assert nfloat({x: sqrt(2)}) == {x: nfloat(sqrt(2))}
     assert nfloat({sqrt(2): x}) == {sqrt(2): x}
     assert nfloat(cos(x + sqrt(2))) == cos(x + nfloat(sqrt(2)))
diff --git a/sympy/core/tests/test_numbers.py b/sympy/core/tests/test_numbers.py
index 0f5cc1a10e..58b5980cab 100644
--- a/sympy/core/tests/test_numbers.py
+++ b/sympy/core/tests/test_numbers.py
@@ -403,11 +403,13 @@ def teq(a):
     teq(2*pi)
     teq(cos(0.1, evaluate=False))
 
+    # long integer
     i = 12345678901234567890
     assert _aresame(Float(12, ''), Float('12', ''))
     assert _aresame(Float(Integer(i), ''), Float(i, ''))
     assert _aresame(Float(i, ''), Float(str(i), 20))
-    assert not _aresame(Float(str(i)), Float(i, ''))
+    assert _aresame(Float(str(i)), Float(i, ''))
+    assert _aresame(Float(i), Float(i, ''))
 
     # inexact floats (repeating binary = denom not multiple of 2)
     # cannot have precision greater than 15
@@ -451,6 +453,11 @@ def teq(a):
     assert '{0:.35f}'.format(Float(pi.n(40), 40)) == '3.14159265358979323846264338327950288'
 
 
+def test_Float_default_to_highprec_from_str():
+    s = str(pi.evalf(128))
+    assert _aresame(Float(s), Float(s, ''))
+
+
 def test_Float_eval():
     a = Float(3.2)
     assert (a**2).is_Float
diff --git a/sympy/core/tests/test_sympify.py b/sympy/core/tests/test_sympify.py
index 147c9dcdd7..2dff7a3b1e 100644
--- a/sympy/core/tests/test_sympify.py
+++ b/sympy/core/tests/test_sympify.py
@@ -1,6 +1,6 @@
-from sympy import Symbol, exp, Integer, Float, sin, cos, log, Poly, Lambda, \
-    Function, I, S, sqrt, srepr, Rational, Tuple, Matrix, Interval, Add, Mul,\
-    Pow, Or, true, false
+from sympy import (Symbol, exp, Integer, Float, sin, cos, log, Poly, Lambda,
+    Function, I, S, sqrt, srepr, Rational, Tuple, Matrix, Interval, Add, Mul,
+    Pow, Or, true, false, Abs, pi)
 from sympy.abc import x, y
 from sympy.core.sympify import sympify, _sympify, SympifyError, kernS
 from sympy.core.decorators import _sympifyit
@@ -486,3 +486,9 @@ def test_issue_5596():
     locals = {}
     exec_("from sympy.abc import Q, C", locals)
     assert str(S('C&Q', locals)) == 'And(C, Q)'
+
+
+def test_issue_8821_highprec_from_str():
+    s = str(pi.evalf(128))
+    p = sympify(s)
+    assert Abs(sin(p)) < 1e-127