Repository: numpy/numpy

Issue:
numpy scalars: should np.set_printoptions(legacy=True) in 0.14.dev reverts to 0.13 printing?
This is another thing we bumped into our scikit-learn doctests using numpy dev. If this is easy for `np.set_printoptions(legacy=True)` to use pre-dragon4 printing it would make our life (and the life of other downstream projects bumping into the same problems with doctests) a bit easier when numpy 1.14 is released.

I think the expected behaviour is that `np.set_printoptions(legacy=True)` does revert to pre-dragon4 format even with all its flaws. If this is not too costly in terms of maintenance, maybe it could be considered? Note we can work-around this issue with doctests ellipsis in scikit-learn.

A snippet to illustrate what I mean in more details:
```py
import numpy as np

try:
    np.set_printoptions(legacy=True)
except TypeError:
    pass

i = 6
numpy_number = np.float64(1.) - i * 1e-17
print('str  : ', numpy_number)
print('repr : ', repr(numpy_number))
```

Output on numpy master:
```
str  :  0.9999999999999999
repr :  0.9999999999999999
```

Output on numpy 1.13:
```
str  :  1.0
repr :  0.99999999999999989
```

When using `np.set_printoptions(legacy=True)` I would expect both output to match.


Discussion:
OK, this is due to our choice to make floating scalars always print like in python3, we discussed this here: https://github.com/numpy/numpy/pull/9966#issuecomment-342016883. In python2 the str of floating scalars gets truncated.

I'll see if we can get it to truncate in python2 in legacy mode.


OK so this is not related to dragon4 but to discrepancies between numpy scalars and Python floats , sorry for the confusion.

I think the more generic questions are: 
1. should numpy 1.14 + `np.set_printoptions(legacy=True)` print/repr exactly the same as numpy 1.13?
1. How hard is it to do?
1. Is it worth the maintenance cost?

As a numpy user, I think the answer for 1. is "ideally yes". There are a bunch of print/repr change from numpy 1.13 to 1.14 and I would expect `np.set_printoptions(legacy=True)` to revert all of them.

numpy developers are better placed to comment on 2.

I guess for 3. a combination of numpy users and developers is needed. The perspective I can give from scikit-learn maintenance point of view: we can work around it for doctests and I am more than fine with working around it.

Just to be explicit, as a scikit-learn maintainer I am of course well-aware of the cost and pain of not being able to get rid of less than optimal code for backward-compatibility reasons.

Resolving patch:
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -42,7 +42,8 @@
 from .umath import absolute, not_equal, isnan, isinf, isfinite
 from . import multiarray
 from .multiarray import (array, dragon4_positional, dragon4_scientific,
-                         datetime_as_string, datetime_data, dtype, ndarray)
+                         datetime_as_string, datetime_data, dtype, ndarray,
+                         set_legacy_print_mode)
 from .fromnumeric import ravel, any
 from .numeric import concatenate, asarray, errstate
 from .numerictypes import (longlong, intc, int_, float_, complex_, bool_,
@@ -242,6 +243,12 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None,
     opt['formatter'] = formatter
     _format_options.update(opt)
 
+    # set the C variable for legacy mode
+    if _format_options['legacy'] == '1.13':
+        set_legacy_print_mode(113)
+    elif _format_options['legacy'] is False:
+        set_legacy_print_mode(0)
+
 
 def get_printoptions():
     """


Test patch:
diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py
--- a/numpy/core/tests/test_arrayprint.py
+++ b/numpy/core/tests/test_arrayprint.py
@@ -447,6 +447,18 @@ def test_floatmode(self):
             "array([1.e+00, 1.e+05, 1.e+10, 1.e+15, 1.e+20])")
         assert_equal(repr(wp), "array([1.234e+001, 1.000e+002, 1.000e+123])")
 
+    def test_legacy_mode_scalars(self):
+        # in legacy mode, str of floats get truncated, and complex scalars
+        # use * for non-finite imaginary part
+        np.set_printoptions(legacy='1.13')
+        assert_equal(str(np.float64(1.123456789123456789)), '1.12345678912')
+        assert_equal(str(np.complex128(complex(1, np.nan))), '(1+nan*j)')
+
+        np.set_printoptions(legacy=False)
+        assert_equal(str(np.float64(1.123456789123456789)),
+                     '1.1234567891234568')
+        assert_equal(str(np.complex128(complex(1, np.nan))), '(1+nanj)')
+
 def test_unicode_object_array():
     import sys
     if sys.version_info[0] >= 3: