Repository: numpy/numpy

Issue:
repr doesn't roundtrip for float32 dtype
It seems that the `np.float32` type doesn't have a round-trippable `repr`:

```python
>>> x = np.float32(1024 - 2**-14)
>>> y = np.float32(1024 - 2**-13)
>>> x == y  # get False, as expected
False
>>> repr(x) == repr(y)  # expecting False
True
>>> np.float32(repr(x)) == x  # expecting True
False
>>> np.float32(repr(y)) == y  # get True, as expected
True
```

Looking at [the source](https://github.com/numpy/numpy/blob/v1.13.0/numpy/core/src/multiarray/scalartypes.c.src#L806), 8 significant digits are used for the `repr` of an `np.float32`, but the IEEE 754 binary32 format requires 9 digits to roundtrip correctly.

Perhaps this is intentional, but it seems surprising.

[Versions: Python 3.6.1, numpy 1.13.0, macOS 10.10.5]
WIP: MAINT: Rewrite LongFloatFormat, trim zeros in scientific notation output
The main purpose of this PR is to implement the new `longfloat` arrayprint formatter, as promised in #9139. 

Now, `longfloat` arrays will print essentially identically to `float` and `double` arrays, using the same code paths. This means `longfloat` arrays now align nicely and have the right number of spaces.

This PR does two additional things which are somewhat unrelated, but are in the same code: I removed trailing zeros in scientific notation, so `1.0000e+100` becomes `1.e+100`, and I made the `longfloat` and `half` formatter precision customizable independently from the float/double precision.

I think this is mostly done, I just need to add a few more tests, eg to test the "locale"-related code, and to test the new precision customization. 

Sidenote: I implemented `format_longfloat` using a "trick" involving temporarily setting the "C" locale (if necessary) which avoids all the complicated manipulation of decimals used in `NumpyOS_ascii_format_double` (which was copied from the CPython code). Resetting the locale was recommended on the gcc page on locales.


Discussion:
Frankly, I find this slightly disturbing, though it must have been there for many many years, should be fixed in any case in my opinion.
> the IEEE 754 binary32 format requires 9 digits to roundtrip correctly.

If that's the case, why is `np.finfo(np.float32).precision == 6`?
@eric-wieser: the `np.finfo` precision values are the maximum decimal precisions for which decimal -> binary -> decimal recovers the original value; for repr, you want something different: the _minimum_ decimal precision for which binary -> decimal -> binary recovers the value. (Assuming IEEE 754 formats, the relevant values are 3 and 5 for `float16`, 6 and 9 for `float32` and 15 and 17 for `float64`.)

Well explained. Seems like we should also expose those larger numbers in `finfo` too then (and perhaps be more precise in the documentation for `precision`). Can you think of a suitable name?

Also, can you cite a source for those numbers?
As far as sources go, the C99 standard has the relevant formulas, in section 5.2.4.2.2p9: for binary -> decimal -> binary roundtrip for a binary format with precision p (which is what we want for `repr`), the formula is `1 + ceil(p * log10(2))`; for p=11, 24 and 53 this gives 5, 9 and 17 respectively. For the precision, we want floor((p-1) * log10(2)), which is where the 3, 6 and 15 values come from.  IEEE 754-2008 also gives the 5, 9 and 17 values explicitly in section 5.12.2

I've never found non-paywalled proofs of those formulas, but they're not hard to prove directly: [here](https://github.com/mdickinson/float-proofs/blob/master/misc/base_conversion_notes.pdf) are some proofs I wrote up last year, after getting annoyed at not finding anything online.

> Seems like we should also expose those larger numbers in finfo too then

Sounds good in principle. The catch would be that C99 provides the precision numbers directly for `float`, `double` and `long double`, without any assumption of IEEE 754, under the names `FLT_DIG`, `DBL_DIG` and `LDBL_DIG`; I assume that that's where `np.finfo` is getting them from. In the other direction, it only defines one number, `DECIMAL_DIG`, which is the:

> number of decimal digits, n, such that any floating-point number in the widest supported floating type with pmax radix b digits can be rounded to a floating-point number with n decimal digits and back again without change to the value,

C11 _does_ provide separate `FLT_DECIMAL_DIG`, `DBL_DECIMAL_DIG` and `LDBL_DECIMAL_DIG` macros, but I don't know how well current compilers support those.

> Can you think of a suitable name?

Not right now. Naming things is hard. :-)
> for a binary format with precision p

Here you're using `precision` to refer to the number of bits in the mantissa, right?

>  The catch would be that C99 provides the precision numbers directly for float, double and long double, without any assumption of IEEE 754, under the names FLT_DIG, DBL_DIG and LDBL_DIG; I assume that that's where np.finfo is getting them from.

Nope, they're hard-coded in the python code, calculated from `finfo.eps`, so this isn't a problem. I don't think the calculations there match your equations though, so perhaps they should be fixed


> Here you're using precision to refer to the number of bits in the mantissa, right?

Aargh, yes. Too many precisions. Yes, the number of bits in the significand, including the implicit bit where relevant.

> Nope, they're hard-coded in the python code, calculated from finfo.eps

Ah, right. I was making bad assumptions, then.

> I don't think the calculations there match your equations though

At a quick glance, it looks the same to me: for a (binary)precision-p binary format, eps should be 2**(1-p), so [`self.precision = int(-log10(self.eps))`](https://github.com/numpy/numpy/blob/v1.13.0/numpy/core/getlimits.py#L81) is computing floor((p-1) * log10(2)).
Is there a reason not just to modify `format_longdouble` and friends, adding extra arguments for the new features? That way, the code path is mostly shared between scalar `repr` and array `repr`.
I left `format_longdouble` alone since I didn't want to modify the scalar printing, which appears to be intentionally different from the array element printing (it includes a trailing 0). I also don't see much point in adding a lot of generalized code for the scalars if we will never use it.

Also, `format_longfoat` was already its own special function in the `multiaraymodule`, so I am just leaving it that way. 
Also, just a comment on the state of float printing in numpy and python: It's messy.

We use C's `printf` to print numpy scalars, but we use python's `printf`-formatting to print array elements, and the two are not the same. CPython actually uses the `dtoa` algorithm for float printing, I think because they decided that the OS `printf` was unreliable and sometimes inaccurate. `dtoa` is advertised to be a highly accurate and also human-friendly way to print floats.

Using C's `printf` is not ideal since we have to to a lot of string manipulation to reformat the OS `printf` output to be what we want (see `numpyos.c`), to account for locale, exponent digits, and other things. CPython falls back to using the same methods if `dtoa.c` is not available for some reason.

Other languages like Julia, Go, Rust, javascript in some browsers, have decided that the OS `printf` was unreliable, and use specific float printing algorithms. They seem to use some combination of `dtoa`, an algorithm called `grisu3` (`dtoa` is apparently based on `grisu2`), and one called `dragon4`.

Here are some links to discussion of these issues:

[Blog Post about Dragon4 and Grisu3](http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/)
[Swift discussion of grisu3 vs dtoa, with more links](https://bugs.swift.org/browse/SR-454)
[Python issue discussing dtoa improvements](https://bugs.python.org/issue9009)

Probably in an ideal world numpy would include a good float-printing algorithm, perhaps customized for our requirements (eg, for output alignment). But we have lived without one until now, so I don't think it's very high priority.
Hmm, some of the numpy problems in my last comment might be solved by using calls to `PyOS_double_to_string` instead of `PyOS_snprintf` in the numpy scalar float formatting code, since the former uses the `dtoa` algorithm. We would need to write wrapper code to account for whitespace padding, exponent padding, like CPython does somewhere in the formatting code too.
One last comment:

I don't think the `dtoa` library can accurately print IEEE-extended precision floats (ie 80-bit `long double` on x64). We may have to rely on the OS printf for that, doubly so because the format of `long double` is arch-dependent.
note: this PR is addressing issue #9699 

Resolving patch:
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -6,7 +6,8 @@
 from __future__ import division, absolute_import, print_function
 
 __all__ = ["array2string", "array_str", "array_repr", "set_string_function",
-           "set_printoptions", "get_printoptions"]
+           "set_printoptions", "get_printoptions", "format_float_positional",
+           "format_float_scientific"]
 __docformat__ = 'restructuredtext'
 
 #
@@ -40,8 +41,8 @@
 from . import numerictypes as _nt
 from .umath import absolute, not_equal, isnan, isinf
 from . import multiarray
-from .multiarray import (array, format_longfloat, datetime_as_string,
-                         datetime_data, dtype, ndarray)
+from .multiarray import (array, dragon4_positional, dragon4_scientific,
+                         datetime_as_string, datetime_data, dtype, ndarray)
 from .fromnumeric import ravel, any
 from .numeric import concatenate, asarray, errstate
 from .numerictypes import (longlong, intc, int_, float_, complex_, bool_,
@@ -58,6 +59,7 @@
 _format_options = {
     'edgeitems': 3,  # repr N leading and trailing items of each dimension
     'threshold': 1000,  # total items > triggers array summarization
+    'floatmode': 'maxprec',
     'precision': 8,  # precision of floating point representations
     'suppress': False,  # suppress printing small floating values in exp format
     'linewidth': 75,
@@ -68,7 +70,7 @@
 
 def _make_options_dict(precision=None, threshold=None, edgeitems=None,
                        linewidth=None, suppress=None, nanstr=None, infstr=None,
-                       sign=None, formatter=None):
+                       sign=None, formatter=None, floatmode=None):
     """ make a dictionary out of the non-None arguments, plus sanity checks """
 
     options = {k: v for k, v in locals().items() if v is not None}
@@ -79,12 +81,17 @@ def _make_options_dict(precision=None, threshold=None, edgeitems=None,
     if sign not in [None, '-', '+', ' ', 'legacy']:
         raise ValueError("sign option must be one of "
                          "' ', '+', '-', or 'legacy'")
+    
+    modes = ['fixed', 'unique', 'maxprec', 'maxprec_equal']
+    if floatmode not in modes + [None]:
+        raise ValueError("floatmode option must be one of " +
+                         ", ".join('"{}"'.format(m) for m in modes))
 
     return options
 
 def set_printoptions(precision=None, threshold=None, edgeitems=None,
                      linewidth=None, suppress=None, nanstr=None, infstr=None,
-                     formatter=None, sign=None):
+                     formatter=None, sign=None, floatmode=None):
     """
     Set printing options.
 
@@ -146,6 +153,23 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None,
             - 'float_kind' : sets 'float' and 'longfloat'
             - 'complex_kind' : sets 'complexfloat' and 'longcomplexfloat'
             - 'str_kind' : sets 'str' and 'numpystr'
+    floatmode : str, optional
+        Controls the interpretation of the `precision` option for
+        floating-point types. Can take the following values:
+            - 'fixed' : Always print exactly `precision` fractional digits,
+                    even if this would print more or fewer digits than
+                    necessary to specify the value uniquely.
+            - 'unique : Print the minimum number of fractional digits necessary
+                    to represent each value uniquely. Different elements may
+                    have a different number of digits. The value of the
+                    `precision` option is ignored.
+            - 'maxprec' : Print at most `precision` fractional digits, but if
+                    an element can be uniquely represented with fewer digits
+                    only print it with that many.
+            - 'maxprec_equal' : Print at most `precision` fractional digits,
+                    but if every element in the array can be uniquely
+                    represented with an equal number of fewer digits, use that
+                    many digits for all elements.
 
     See Also
     --------
@@ -196,7 +220,8 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None,
     ... suppress=False, threshold=1000, formatter=None)
     """
     opt = _make_options_dict(precision, threshold, edgeitems, linewidth,
-                             suppress, nanstr, infstr, sign, formatter)
+                             suppress, nanstr, infstr, sign, formatter,
+                             floatmode)
     # formatter is always reset
     opt['formatter'] = formatter
     _format_options.update(opt)
@@ -259,15 +284,16 @@ def repr_format(x):
     return repr(x)
 
 def _get_formatdict(data, **opt):
-    prec, supp, sign = opt['precision'], opt['suppress'], opt['sign']
+    prec, fmode = opt['precision'], opt['floatmode']
+    supp, sign = opt['suppress'], opt['sign']
 
     # wrapped in lambdas to avoid taking a code path with the wrong type of data
     formatdict = {'bool': lambda: BoolFormat(data),
                   'int': lambda: IntegerFormat(data),
-                  'float': lambda: FloatFormat(data, prec, supp, sign),
-                  'longfloat': lambda: LongFloatFormat(prec),
-                  'complexfloat': lambda: ComplexFormat(data, prec, supp, sign),
-                  'longcomplexfloat': lambda: LongComplexFormat(prec),
+                  'float': lambda: FloatingFormat(data, prec, fmode, 
+                                                  supp, sign),
+                  'complexfloat': lambda: ComplexFormat(data, prec, fmode,
+                                                        supp, sign),
                   'datetime': lambda: DatetimeFormat(data),
                   'timedelta': lambda: TimedeltaFormat(data),
                   'object': lambda: _object_format,
@@ -289,7 +315,7 @@ def indirect(x):
             for key in ['int']:
                 formatdict[key] = indirect(formatter['int_kind'])
         if 'float_kind' in fkeys:
-            for key in ['float', 'longfloat']:
+            for key in ['half', 'float', 'longfloat']:
                 formatdict[key] = indirect(formatter['float_kind'])
         if 'complex_kind' in fkeys:
             for key in ['complexfloat', 'longcomplexfloat']:
@@ -321,15 +347,9 @@ def _get_format_function(data, **options):
         else:
             return formatdict['int']()
     elif issubclass(dtypeobj, _nt.floating):
-        if issubclass(dtypeobj, _nt.longfloat):
-            return formatdict['longfloat']()
-        else:
-            return formatdict['float']()
+        return formatdict['float']()
     elif issubclass(dtypeobj, _nt.complexfloating):
-        if issubclass(dtypeobj, _nt.clongfloat):
-            return formatdict['longcomplexfloat']()
-        else:
-            return formatdict['complexfloat']()
+        return formatdict['complexfloat']()
     elif issubclass(dtypeobj, (_nt.unicode_, _nt.string_)):
         return formatdict['numpystr']()
     elif issubclass(dtypeobj, _nt.datetime64):
@@ -396,7 +416,7 @@ def _array2string(a, options, separator=' ', prefix=""):
 def array2string(a, max_line_width=None, precision=None,
                  suppress_small=None, separator=' ', prefix="",
                  style=np._NoValue, formatter=None, threshold=None,
-                 edgeitems=None, sign=None):
+                 edgeitems=None, sign=None, floatmode=None):
     """
     Return a string representation of an array.
 
@@ -463,6 +483,23 @@ def array2string(a, max_line_width=None, precision=None,
         (whitespace character) in the sign position of positive values.  If
         '-', omit the sign character of positive values. If 'legacy', print a
         space for positive values except in 0d arrays.
+    floatmode : str, optional
+        Controls the interpretation of the `precision` option for
+        floating-point types. Can take the following values:
+            - 'fixed' : Always print exactly `precision` fractional digits,
+                    even if this would print more or fewer digits than
+                    necessary to specify the value uniquely.
+            - 'unique : Print the minimum number of fractional digits necessary
+                    to represent each value uniquely. Different elements may
+                    have a different number of digits.  The value of the
+                    `precision` option is ignored.
+            - 'maxprec' : Print at most `precision` fractional digits, but if
+                    an element can be uniquely represented with fewer digits
+                    only print it with that many.
+            - 'maxprec_equal' : Print at most `precision` fractional digits,
+                    but if every element in the array can be uniquely
+                    represented with an equal number of fewer digits, use that
+                    many digits for all elements.
 
     Returns
     -------
@@ -510,7 +547,7 @@ def array2string(a, max_line_width=None, precision=None,
 
     overrides = _make_options_dict(precision, threshold, edgeitems,
                                    max_line_width, suppress_small, None, None,
-                                   sign, formatter)
+                                   sign, formatter, floatmode)
     options = _format_options.copy()
     options.update(overrides)
 
@@ -597,8 +634,8 @@ def _formatArray(a, format_function, rank, max_line_len,
                           summary_insert).rstrip()+']\n'
     return s
 
-class FloatFormat(object):
-    def __init__(self, data, precision, suppress_small, sign=False):
+class FloatingFormat(object):
+    def __init__(self, data, precision, floatmode, suppress_small, sign=False):
         # for backcompatibility, accept bools
         if isinstance(sign, bool):
             sign = '+' if sign else '-'
@@ -607,8 +644,15 @@ def __init__(self, data, precision, suppress_small, sign=False):
         if sign == 'legacy':
             self._legacy = True
             sign = '-' if data.shape == () else ' '
-
-        self.precision = precision
+        
+        self.floatmode = floatmode
+        if floatmode == 'unique':
+            self.precision = -1
+        else:
+            if precision < 0:
+                raise ValueError(
+                    "precision must be >= 0 in {} mode".format(floatmode))
+            self.precision = precision
         self.suppress_small = suppress_small
         self.sign = sign
         self.exp_format = False
@@ -637,74 +681,230 @@ def fillFormat(self, data):
                                            or max_val/min_val > 1000.):
                     self.exp_format = True
 
-        if self.exp_format:
-            self.large_exponent = 0 < min_val < 1e-99 or max_val >= 1e100
-
-            signpos = self.sign != '-' or any(non_zero < 0)
-            # for back-compatibility with np 1.13, use two spaces
+        if len(non_zero) == 0:
+            self.pad_left = 0
+            self.pad_right = 0
+            self.trim = '.'
+            self.exp_size = -1
+            self.unique = True
+        elif self.exp_format:
+            # first pass printing to determine sizes
+            trim, unique = '.', True
+            if self.floatmode == 'fixed' or self._legacy:
+                trim, unique = 'k', False
+            strs = (dragon4_scientific(x, precision=self.precision,
+                               unique=unique, trim=trim, sign=self.sign == '+')
+                    for x in non_zero)
+            frac_strs, _, exp_strs = zip(*(s.partition('e') for s in strs))
+            int_part, frac_part = zip(*(s.split('.') for s in frac_strs))
+            self.exp_size = max(len(s) for s in exp_strs) - 1
+
+            self.trim = 'k'
+            self.precision = max(len(s) for s in frac_part)
+
+            # for back-compatibility with np 1.13, use two spaces and full prec
             if self._legacy:
-                signpos = 2
-            max_str_len = signpos + 6 + self.precision + self.large_exponent
+                self.pad_left = 2 + (not (all(non_zero > 0) and self.sign == ' '))
+            else:
+                # this should be only 1 or two. Can be calculated from sign.
+                self.pad_left = max(len(s) for s in int_part)
+            # pad_right is not used to print, but needed for nan length calculation
+            self.pad_right = self.exp_size + 2 + self.precision
 
-            conversion = '' if self.sign == '-' else self.sign
-            format = '%' + conversion + '%d.%de' % (max_str_len, self.precision)
+            self.unique = False
         else:
-            if len(non_zero) and self.precision > 0:
-                precision = self.precision
-                trim_zero = lambda s: precision - (len(s) - len(s.rstrip('0')))
-                fmt = '%%.%df' % (precision,)
-                precision = max(trim_zero(fmt % x) for x in abs_non_zero)
+            # first pass printing to determine sizes
+            trim, unique = '.', True
+            if self.floatmode == 'fixed':
+                trim, unique = 'k', False
+            strs = (dragon4_positional(x, precision=self.precision,
+                                       unique=unique, trim=trim,
+                                       sign=self.sign == '+')
+                    for x in non_zero)
+            int_part, frac_part = zip(*(s.split('.') for s in strs))
+            self.pad_left = max(len(s) for s in int_part)
+            self.pad_right = max(len(s) for s in frac_part)
+            self.exp_size = -1
+
+            if self.floatmode in ['fixed', 'maxprec_equal']:
+                self.precision = self.pad_right
+                self.unique = False
+                self.trim = 'k'
             else:
-                precision = 0
-
-            int_len = len(str(int(max_val)))
-            signpos = self.sign != '-' or (len(str(int(min_val_sgn))) > int_len)
-            max_str_len = signpos + int_len + 1 + precision
-
-            if any(special):
-                neginf = self.sign != '-' or any(data[hasinf] < 0)
-                nanlen = len(_format_options['nanstr'])
-                inflen = len(_format_options['infstr']) + neginf
-                max_str_len = max(max_str_len, nanlen, inflen)
-
-            conversion = '' if self.sign == '-' else self.sign
-            format = '%#' + conversion + '%d.%df' % (max_str_len, precision)
-
-        self.special_fmt = '%%%ds' % (max_str_len,)
-        self.format = format
-
-    def __call__(self, x, strip_zeros=True):
-        with errstate(invalid='ignore'):
-            if isnan(x):
-                nan_str = _format_options['nanstr']
-                if self.sign == '+':
-                    return self.special_fmt % ('+' + nan_str,)
-                else:
-                    return self.special_fmt % (nan_str,)
-            elif isinf(x):
-                inf_str = _format_options['infstr']
-                if x > 0:
-                    if self.sign == '+':
-                        return self.special_fmt % ('+' + inf_str,)
-                    else:
-                        return self.special_fmt % (inf_str,)
-                else:
-                    return self.special_fmt % ('-' + inf_str,)
-
-        s = self.format % x
-        if self.large_exponent:
-            # 3-digit exponent
-            expsign = s[-3]
-            if expsign == '+' or expsign == '-':
-                s = s[1:-2] + '0' + s[-2:]
-        elif self.exp_format:
-            # 2-digit exponent
-            if s[-3] == '0':
-                s = ' ' + s[:-3] + s[-2:]
-        elif strip_zeros:
-            z = s.rstrip('0')
-            s = z + ' '*(len(s)-len(z))
-        return s
+                self.unique = True
+                self.trim = '.'
+
+        # account for sign = ' ' by adding one to pad_left
+        if len(non_zero) > 0 and all(non_zero > 0) and self.sign == ' ':
+            self.pad_left += 1
+
+        if any(special):
+            neginf = self.sign != '-' or any(data[hasinf] < 0)
+            nanlen = len(_format_options['nanstr'])
+            inflen = len(_format_options['infstr']) + neginf
+            offset = self.pad_right + 1  # +1 for decimal pt
+            self.pad_left = max(self.pad_left, nanlen - offset, inflen - offset)
+
+    def __call__(self, x):
+        if not np.isfinite(x):
+            with errstate(invalid='ignore'):
+                if np.isnan(x):
+                    sign = '+' if self.sign == '+' else ''
+                    ret = sign + _format_options['nanstr']
+                else:  # isinf
+                    sign = '-' if x < 0 else '+' if self.sign == '+' else ''
+                    ret = sign + _format_options['infstr']
+                return ' '*(self.pad_left + self.pad_right + 1 - len(ret)) + ret
+
+        if self.exp_format:
+            return dragon4_scientific(x, precision=self.precision,
+                                      unique=self.unique,
+                                      trim=self.trim, sign=self.sign == '+',
+                                      pad_left=self.pad_left,
+                                      exp_digits=self.exp_size)
+        else:
+            return dragon4_positional(x, precision=self.precision,
+                                      unique=self.unique,
+                                      trim=self.trim, sign=self.sign == '+',
+                                      pad_left=self.pad_left,
+                                      pad_right=self.pad_right)
+
+
+def format_float_scientific(x, precision=None, unique=True, trim='k',
+                            sign=False, pad_left=None, exp_digits=None):
+    """
+    Format a floating-point scalar as a string in fractional notation.
+
+    Provides control over rounding, trimming and padding. Uses and assumes
+    IEEE unbiased rounding. Uses the "Dragon4" algorithm.
+
+    Parameters
+    ----------
+    x : python float or numpy floating scalar
+        Value to format.
+    precision : non-negative integer, optional
+        Maximum number of fractional digits to print. May be ommited
+        if `unique` is `True`, but is required if unique is `False`.
+    unique : boolean, optional
+        If `False`, output exactly `precision` fractional digits and round the
+        remaining value. Digits are generated as if printing an
+        infinite-precision value and stopping after `precision` digits.
+        If `True`, use a digit-generation strategy which gives the shortest
+        representation which uniquely identifies the floating-point number from
+        other values of the same type, by judicious rounding. If `precision`
+        was omitted, print out the full unique representation, otherwise digit
+        generation is cut off after `precision` digits and the remaining value
+        is rounded.
+    trim : one of 'k', '.', '0', '-', optional
+        Controls post-processing trimming of trailing digits, as follows:
+            k : keep trailing zeros, keep decimal point (no trimming)
+            . : trim all trailing zeros, leave decimal point
+            0 : trim all but the zero before the decimal point. Insert the
+                zero if it is missing.
+            - : trim trailing zeros and any trailing decimal point
+    sign : boolean, optional
+        Whether to show the sign for positive values.
+    pad_left : non-negative integer, optional
+        Pad the left side of the string with whitespace until at least that
+        many characters are to the left of the decimal point.
+    exp_digits : non-negative integer, optional
+        Pad the exponent with zeros until it contains at least this many digits.
+        If omitted, the exponent will be at least 2 digits.
+
+    Returns
+    -------
+    rep : string
+        The string representation of the floating point value
+
+    See Also
+    --------
+    format_float_positional
+
+    Examples
+    --------
+    >>> np.format_float_scientific(np.float32(np.pi))
+    '3.1415927e+00'
+    >>> s = np.float32(1.23e24)
+    >>> np.format_float_scientific(s, unique=False, precision=15)
+    '1.230000071797338e+24'
+    >>> np.format_float_scientific(s, exp_digits=4)
+    '1.23e+0024'
+    """
+    precision = -1 if precision is None else precision
+    pad_left = -1 if pad_left is None else pad_left
+    exp_digits = -1 if exp_digits is None else exp_digits
+    return dragon4_scientific(x, precision=precision, unique=unique, trim=trim,
+                              sign=sign, pad_left=pad_left,
+                              exp_digits=exp_digits)
+
+def format_float_positional(x, precision=None, unique=True, trim='k',
+                            sign=False, pad_left=None, pad_right=None):
+    """
+    Format a floating-point scalar as a string in scientific notation.
+
+    Provides control over rounding, trimming and padding.  Uses and assumes
+    IEEE unbiased rounding. Uses the "Dragon4" algorithm.
+
+    Parameters
+    ----------
+    x : python float or numpy floating scalar
+        Value to format.
+    precision : non-negative integer, optional
+        Maximum number of fractional digits to print. May be ommited
+        if `unique` is `True`, but is required if unique is `False`.
+    unique : boolean, optional
+        If `False`, output exactly `precision` fractional digits and round the
+        remaining value. Digits are generated as if printing an
+        infinite-precision value and stopping after `precision` digits.
+        If `True`, use a digit-generation strategy which gives the shortest
+        representation which uniquely identifies the floating-point number from
+        other values of the same type, by judicious rounding. If `precision`
+        was omitted, print out the full unique representation, otherwise digit
+        generation is cut off after `precision` digits and the remaining value
+        is rounded.
+    trim : one of 'k', '.', '0', '-', optional
+        Controls post-processing trimming of trailing digits, as follows:
+            k : keep trailing zeros, keep decimal point (no trimming)
+            . : trim all trailing zeros, leave decimal point
+            0 : trim all but the zero before the decimal point. Insert the
+                zero if it is missing.
+            - : trim trailing zeros and any trailing decimal point
+    sign : boolean, optional
+        Whether to show the sign for positive values.
+    pad_left : non-negative integer, optional
+        Pad the left side of the string with whitespace until at least that
+        many characters are to the left of the decimal point.
+    pad_right : non-negative integer, optional
+        Pad the right side of the string with whitespace until at least that
+        many characters are to the right of the decimal point.
+
+    Returns
+    -------
+    rep : string
+        The string representation of the floating point value
+
+    See Also
+    --------
+    format_float_scientific
+
+    Examples
+    --------
+    >>> np.format_float_scientific(np.float32(np.pi))
+    '3.1415927'
+    >>> np.format_float_positional(np.float16(np.pi))
+    '3.14'
+    >>> np.format_float_positional(np.float16(0.3))
+    '0.3'
+    >>> np.format_float_positional(np.float16(0.3), unique=False, precision=10)
+    '0.3000488281'
+    """
+    precision = -1 if precision is None else precision
+    pad_left = -1 if pad_left is None else pad_left
+    pad_right = -1 if pad_right is None else pad_right
+    return dragon4_positional(x, precision=precision, unique=unique, trim=trim,
+                              sign=sign, pad_left=pad_left,
+                              pad_right=pad_right)
+
 
 class IntegerFormat(object):
     def __init__(self, data):
@@ -726,6 +926,7 @@ def __call__(self, x):
         else:
             return "%s" % x
 
+
 class BoolFormat(object):
     def __init__(self, data, **kwargs):
         # add an extra space so " True" and "False" have the same length and
@@ -736,75 +937,22 @@ def __call__(self, x):
         return self.truestr if x else "False"
 
 
-class LongFloatFormat(object):
-    # XXX Have to add something to determine the width to use a la FloatFormat
-    # Right now, things won't line up properly
-    def __init__(self, precision, sign=False):
+class ComplexFormat(object):
+    def __init__(self, x, precision, floatmode, suppress_small, sign=False):
         # for backcompatibility, accept bools
         if isinstance(sign, bool):
             sign = '+' if sign else '-'
 
-        self.precision = precision
-        self.sign = sign
-
-    def __call__(self, x):
-        if isnan(x):
-            nan_str = _format_options['nanstr']
-            if self.sign == '+':
-                return '+' + nan_str
-            else:
-                return ' ' + nan_str
-        elif isinf(x):
-            inf_str = _format_options['infstr']
-            if x > 0:
-                if self.sign == '+':
-                    return '+' + inf_str
-                else:
-                    return ' ' + inf_str
-            else:
-                return '-' + inf_str
-        elif x >= 0:
-            if self.sign == '+':
-                return '+' + format_longfloat(x, self.precision)
-            else:
-                return ' ' + format_longfloat(x, self.precision)
-        else:
-            return format_longfloat(x, self.precision)
-
-
-class LongComplexFormat(object):
-    def __init__(self, precision):
-        self.real_format = LongFloatFormat(precision)
-        self.imag_format = LongFloatFormat(precision, sign='+')
+        self.real_format = FloatingFormat(x.real, precision, floatmode, 
+                                          suppress_small, sign=sign)
+        self.imag_format = FloatingFormat(x.imag, precision, floatmode, 
+                                          suppress_small, sign='+')
 
     def __call__(self, x):
         r = self.real_format(x.real)
         i = self.imag_format(x.imag)
         return r + i + 'j'
 
-
-class ComplexFormat(object):
-    def __init__(self, x, precision, suppress_small, sign=False):
-        # for backcompatibility, accept bools
-        if isinstance(sign, bool):
-            sign = '+' if sign else '-'
-
-        self.real_format = FloatFormat(x.real, precision, suppress_small,
-                                       sign=sign)
-        self.imag_format = FloatFormat(x.imag, precision, suppress_small,
-                                       sign='+')
-
-    def __call__(self, x):
-        r = self.real_format(x.real, strip_zeros=False)
-        i = self.imag_format(x.imag, strip_zeros=False)
-        if not self.imag_format.exp_format:
-            z = i.rstrip('0')
-            i = z + 'j' + ' '*(len(i)-len(z))
-        else:
-            i = i + 'j'
-        return r + i
-
-
 class DatetimeFormat(object):
     def __init__(self, x, unit=None, timezone=None, casting='same_kind'):
         # Get the unit from the dtype
@@ -826,6 +974,7 @@ def __call__(self, x):
                                     timezone=self.timezone,
                                     casting=self.casting)
 
+
 class TimedeltaFormat(object):
     def __init__(self, data):
         nat_value = array(['NaT'], dtype=data.dtype)[0]
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -728,6 +728,7 @@ def get_mathlib_info(*args):
             join('src', 'multiarray', 'conversion_utils.h'),
             join('src', 'multiarray', 'ctors.h'),
             join('src', 'multiarray', 'descriptor.h'),
+            join('src', 'multiarray', 'dragon4.h'),
             join('src', 'multiarray', 'getset.h'),
             join('src', 'multiarray', 'hashdescr.h'),
             join('src', 'multiarray', 'iterators.h'),
@@ -793,6 +794,7 @@ def get_mathlib_info(*args):
             join('src', 'multiarray', 'datetime_busday.c'),
             join('src', 'multiarray', 'datetime_busdaycal.c'),
             join('src', 'multiarray', 'descriptor.c'),
+            join('src', 'multiarray', 'dragon4.c'),
             join('src', 'multiarray', 'dtype_transfer.c'),
             join('src', 'multiarray', 'einsum.c.src'),
             join('src', 'multiarray', 'flagsobject.c'),


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
@@ -72,42 +72,42 @@ def test_str(self):
         dtypes = [np.complex64, np.cdouble, np.clongdouble]
         actual = [str(np.array([c], dt)) for c in cvals for dt in dtypes]
         wanted = [
-            '[0.+0.j]',    '[0.+0.j]',    '[ 0.0+0.0j]',
-            '[0.+1.j]',    '[0.+1.j]',    '[ 0.0+1.0j]',
-            '[0.-1.j]',    '[0.-1.j]',    '[ 0.0-1.0j]',
-            '[0.+infj]',   '[0.+infj]',   '[ 0.0+infj]',
-            '[0.-infj]',   '[0.-infj]',   '[ 0.0-infj]',
-            '[0.+nanj]',   '[0.+nanj]',   '[ 0.0+nanj]',
-            '[1.+0.j]',    '[1.+0.j]',    '[ 1.0+0.0j]',
-            '[1.+1.j]',    '[1.+1.j]',    '[ 1.0+1.0j]',
-            '[1.-1.j]',    '[1.-1.j]',    '[ 1.0-1.0j]',
-            '[1.+infj]',   '[1.+infj]',   '[ 1.0+infj]',
-            '[1.-infj]',   '[1.-infj]',   '[ 1.0-infj]',
-            '[1.+nanj]',   '[1.+nanj]',   '[ 1.0+nanj]',
-            '[-1.+0.j]',    '[-1.+0.j]',    '[-1.0+0.0j]',
-            '[-1.+1.j]',    '[-1.+1.j]',    '[-1.0+1.0j]',
-            '[-1.-1.j]',    '[-1.-1.j]',    '[-1.0-1.0j]',
-            '[-1.+infj]',   '[-1.+infj]',   '[-1.0+infj]',
-            '[-1.-infj]',   '[-1.-infj]',   '[-1.0-infj]',
-            '[-1.+nanj]',   '[-1.+nanj]',   '[-1.0+nanj]',
-            '[inf+0.j]',   '[inf+0.j]',   '[ inf+0.0j]',
-            '[inf+1.j]',   '[inf+1.j]',   '[ inf+1.0j]',
-            '[inf-1.j]',   '[inf-1.j]',   '[ inf-1.0j]',
-            '[inf+infj]',  '[inf+infj]',  '[ inf+infj]',
-            '[inf-infj]',  '[inf-infj]',  '[ inf-infj]',
-            '[inf+nanj]',  '[inf+nanj]',  '[ inf+nanj]',
-            '[-inf+0.j]',   '[-inf+0.j]',   '[-inf+0.0j]',
-            '[-inf+1.j]',   '[-inf+1.j]',   '[-inf+1.0j]',
-            '[-inf-1.j]',   '[-inf-1.j]',   '[-inf-1.0j]',
-            '[-inf+infj]',  '[-inf+infj]',  '[-inf+infj]',
-            '[-inf-infj]',  '[-inf-infj]',  '[-inf-infj]',
-            '[-inf+nanj]',  '[-inf+nanj]',  '[-inf+nanj]',
-            '[nan+0.j]',   '[nan+0.j]',   '[ nan+0.0j]',
-            '[nan+1.j]',   '[nan+1.j]',   '[ nan+1.0j]',
-            '[nan-1.j]',   '[nan-1.j]',   '[ nan-1.0j]',
-            '[nan+infj]',  '[nan+infj]',  '[ nan+infj]',
-            '[nan-infj]',  '[nan-infj]',  '[ nan-infj]',
-            '[nan+nanj]',  '[nan+nanj]',  '[ nan+nanj]']
+            '[0.+0.j]',    '[0.+0.j]',    '[0.+0.j]',
+            '[0.+1.j]',    '[0.+1.j]',    '[0.+1.j]',
+            '[0.-1.j]',    '[0.-1.j]',    '[0.-1.j]',
+            '[0.+infj]',   '[0.+infj]',   '[0.+infj]',
+            '[0.-infj]',   '[0.-infj]',   '[0.-infj]',
+            '[0.+nanj]',   '[0.+nanj]',   '[0.+nanj]',
+            '[1.+0.j]',    '[1.+0.j]',    '[1.+0.j]',
+            '[1.+1.j]',    '[1.+1.j]',    '[1.+1.j]',
+            '[1.-1.j]',    '[1.-1.j]',    '[1.-1.j]',
+            '[1.+infj]',   '[1.+infj]',   '[1.+infj]',
+            '[1.-infj]',   '[1.-infj]',   '[1.-infj]',
+            '[1.+nanj]',   '[1.+nanj]',   '[1.+nanj]',
+            '[-1.+0.j]',   '[-1.+0.j]',   '[-1.+0.j]',
+            '[-1.+1.j]',   '[-1.+1.j]',   '[-1.+1.j]',
+            '[-1.-1.j]',   '[-1.-1.j]',   '[-1.-1.j]',
+            '[-1.+infj]',  '[-1.+infj]',  '[-1.+infj]',
+            '[-1.-infj]',  '[-1.-infj]',  '[-1.-infj]',
+            '[-1.+nanj]',  '[-1.+nanj]',  '[-1.+nanj]',
+            '[inf+0.j]',   '[inf+0.j]',   '[inf+0.j]',
+            '[inf+1.j]',   '[inf+1.j]',   '[inf+1.j]',
+            '[inf-1.j]',   '[inf-1.j]',   '[inf-1.j]',
+            '[inf+infj]',  '[inf+infj]',  '[inf+infj]',
+            '[inf-infj]',  '[inf-infj]',  '[inf-infj]',
+            '[inf+nanj]',  '[inf+nanj]',  '[inf+nanj]',
+            '[-inf+0.j]',  '[-inf+0.j]',  '[-inf+0.j]',
+            '[-inf+1.j]',  '[-inf+1.j]',  '[-inf+1.j]',
+            '[-inf-1.j]',  '[-inf-1.j]',  '[-inf-1.j]',
+            '[-inf+infj]', '[-inf+infj]', '[-inf+infj]',
+            '[-inf-infj]', '[-inf-infj]', '[-inf-infj]',
+            '[-inf+nanj]', '[-inf+nanj]', '[-inf+nanj]',
+            '[nan+0.j]',   '[nan+0.j]',   '[nan+0.j]',
+            '[nan+1.j]',   '[nan+1.j]',   '[nan+1.j]',
+            '[nan-1.j]',   '[nan-1.j]',   '[nan-1.j]',
+            '[nan+infj]',  '[nan+infj]',  '[nan+infj]',
+            '[nan-infj]',  '[nan-infj]',  '[nan-infj]',
+            '[nan+nanj]',  '[nan+nanj]',  '[nan+nanj]']
 
         for res, val in zip(actual, wanted):
             assert_equal(res, val)
@@ -290,22 +290,23 @@ def test_sign_spacing(self):
 
         assert_equal(repr(a), 'array([0., 1., 2., 3.])')
         assert_equal(repr(np.array(1.)), 'array(1.)')
-        assert_equal(repr(b), 'array([1.23400000e+09])')
+        assert_equal(repr(b), 'array([1.234e+09])')
 
         np.set_printoptions(sign=' ')
         assert_equal(repr(a), 'array([ 0.,  1.,  2.,  3.])')
         assert_equal(repr(np.array(1.)), 'array( 1.)')
-        assert_equal(repr(b), 'array([ 1.23400000e+09])')
+        assert_equal(repr(b), 'array([ 1.234e+09])')
 
         np.set_printoptions(sign='+')
         assert_equal(repr(a), 'array([+0., +1., +2., +3.])')
         assert_equal(repr(np.array(1.)), 'array(+1.)')
-        assert_equal(repr(b), 'array([+1.23400000e+09])')
+        assert_equal(repr(b), 'array([+1.234e+09])')
 
         np.set_printoptions(sign='legacy')
         assert_equal(repr(a), 'array([ 0.,  1.,  2.,  3.])')
         assert_equal(repr(np.array(1.)), 'array(1.)')
-        assert_equal(repr(b), 'array([  1.23400000e+09])')
+        assert_equal(repr(b),  'array([  1.23400000e+09])')
+        assert_equal(repr(-b), 'array([ -1.23400000e+09])')
 
     def test_sign_spacing_structured(self):
         a = np.ones(2, dtype='f,f')
@@ -313,6 +314,86 @@ def test_sign_spacing_structured(self):
                               "      dtype=[('f0', '<f4'), ('f1', '<f4')])")
         assert_equal(repr(a[0]), "(1., 1.)")
 
+    def test_floatmode(self):
+        x = np.array([0.6104, 0.922, 0.457, 0.0906, 0.3733, 0.007244,
+                      0.5933, 0.947, 0.2383, 0.4226], dtype=np.float16)
+        y = np.array([0.2918820979355541, 0.5064172631089138,
+                      0.2848750619642916, 0.4342965294660567,
+                      0.7326538397312751, 0.3459503329096204,
+                      0.0862072768214508, 0.39112753029631175],
+                      dtype=np.float64)
+        z = np.arange(6, dtype=np.float16)/10
+
+        # also make sure 1e23 is right (is between two fp numbers)
+        w = np.array(['1e{}'.format(i) for i in range(25)], dtype=np.float64)
+        # note: we construct w from the strings `1eXX` instead of doing
+        # `10.**arange(24)` because it turns out the two are not equivalent in
+        # python. On some architectures `1e23 != 10.**23`.
+        wp = np.array([1.234e1, 1e2, 1e123])
+
+        # unique mode
+        np.set_printoptions(floatmode='unique')
+        assert_equal(repr(x),
+    "array([0.6104  , 0.922   , 0.457   , 0.0906  , 0.3733  , 0.007244,\n"
+    "       0.5933  , 0.947   , 0.2383  , 0.4226  ], dtype=float16)")
+        assert_equal(repr(y),
+    "array([0.2918820979355541 , 0.5064172631089138 , 0.2848750619642916 ,\n"
+    "       0.4342965294660567 , 0.7326538397312751 , 0.3459503329096204 ,\n"
+    "       0.0862072768214508 , 0.39112753029631175])")
+        assert_equal(repr(z),
+    "array([0. , 0.1, 0.2, 0.3, 0.4, 0.5], dtype=float16)")
+        assert_equal(repr(w),
+    "array([1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06, 1.e+07,\n"
+    "       1.e+08, 1.e+09, 1.e+10, 1.e+11, 1.e+12, 1.e+13, 1.e+14, 1.e+15,\n"
+    "       1.e+16, 1.e+17, 1.e+18, 1.e+19, 1.e+20, 1.e+21, 1.e+22, 1.e+23,\n"
+    "       1.e+24])")
+        assert_equal(repr(wp), "array([1.234e+001, 1.000e+002, 1.000e+123])")
+
+        # maxprec mode, precision=8
+        np.set_printoptions(floatmode='maxprec', precision=8)
+        assert_equal(repr(x),
+            "array([0.6104  , 0.922   , 0.457   , 0.0906  , 0.3733  , 0.007244,\n"
+            "       0.5933  , 0.947   , 0.2383  , 0.4226  ], dtype=float16)")
+        assert_equal(repr(y),
+            "array([0.2918821 , 0.50641726, 0.28487506, 0.43429653, 0.73265384,\n"
+            "       0.34595033, 0.08620728, 0.39112753])")
+        assert_equal(repr(z),
+            "array([0. , 0.1, 0.2, 0.3, 0.4, 0.5], dtype=float16)")
+        assert_equal(repr(w[::5]),
+            "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])")
+
+        # fixed mode, precision=4
+        np.set_printoptions(floatmode='fixed', precision=4)
+        assert_equal(repr(x),
+    "array([0.6104, 0.9219, 0.4570, 0.0906, 0.3733, 0.0072, 0.5933, 0.9468,\n"
+    "       0.2383, 0.4226], dtype=float16)")
+        assert_equal(repr(y),
+    "array([0.2919, 0.5064, 0.2849, 0.4343, 0.7327, 0.3460, 0.0862, 0.3911])")
+        assert_equal(repr(z),
+    "array([0.0000, 0.1000, 0.2000, 0.3000, 0.3999, 0.5000], dtype=float16)")
+        assert_equal(repr(w[::5]),
+    "array([1.0000e+00, 1.0000e+05, 1.0000e+10, 1.0000e+15, 1.0000e+20])")
+        assert_equal(repr(wp), "array([1.2340e+001, 1.0000e+002, 1.0000e+123])")
+        # for larger precision, representation error becomes more apparent:
+        np.set_printoptions(floatmode='fixed', precision=8)
+        assert_equal(repr(z),
+    "array([0.00000000, 0.09997559, 0.19995117, 0.30004883, 0.39990234,\n"
+    "       0.50000000], dtype=float16)")
+
+        # maxprec_equal  mode, precision=8
+        np.set_printoptions(floatmode='maxprec_equal', precision=8)
+        assert_equal(repr(x),
+    "array([0.610352, 0.921875, 0.457031, 0.090576, 0.373291, 0.007244,\n"
+    "       0.593262, 0.946777, 0.238281, 0.422607], dtype=float16)")
+        assert_equal(repr(y),
+    "array([0.29188210, 0.50641726, 0.28487506, 0.43429653, 0.73265384,\n"
+    "       0.34595033, 0.08620728, 0.39112753])")
+        assert_equal(repr(z),
+            "array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5], dtype=float16)")
+        assert_equal(repr(w[::5]),
+            "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_unicode_object_array():
     import sys
diff --git a/numpy/core/tests/test_getlimits.py b/numpy/core/tests/test_getlimits.py
--- a/numpy/core/tests/test_getlimits.py
+++ b/numpy/core/tests/test_getlimits.py
@@ -80,7 +80,7 @@ def test_iinfo_repr(self):
         assert_equal(repr(np.iinfo(np.int16)), expected)
 
     def test_finfo_repr(self):
-        expected = "finfo(resolution=1e-06, min=-3.4028235e+38," + \
+        expected = "finfo(resolution=0.000001, min=-3.4028235e+38," + \
                    " max=3.4028235e+38, dtype=float32)"
         assert_equal(repr(np.finfo(np.float32)), expected)
 
diff --git a/numpy/core/tests/test_print.py b/numpy/core/tests/test_print.py
--- a/numpy/core/tests/test_print.py
+++ b/numpy/core/tests/test_print.py
@@ -23,12 +23,12 @@ def check_float_type(tp):
         assert_equal(str(tp(x)), str(float(x)),
                      err_msg='Failed str formatting for type %s' % tp)
 
-    if tp(1e10).itemsize > 4:
-        assert_equal(str(tp(1e10)), str(float('1e10')),
+    if tp(1e16).itemsize > 4:
+        assert_equal(str(tp(1e16)), str(float('1e16')),
                      err_msg='Failed str formatting for type %s' % tp)
     else:
-        ref = '1e+10'
-        assert_equal(str(tp(1e10)), ref,
+        ref = '1e+16'
+        assert_equal(str(tp(1e16)), ref,
                      err_msg='Failed str formatting for type %s' % tp)
 
 def test_float_types():
@@ -67,12 +67,12 @@ def check_complex_type(tp):
         assert_equal(str(tp(x + x*1j)), str(complex(x + x*1j)),
                      err_msg='Failed str formatting for type %s' % tp)
 
-    if tp(1e10).itemsize > 8:
-        assert_equal(str(tp(1e10)), str(complex(1e10)),
+    if tp(1e16).itemsize > 8:
+        assert_equal(str(tp(1e16)), str(complex(1e16)),
                      err_msg='Failed str formatting for type %s' % tp)
     else:
-        ref = '(1e+10+0j)'
-        assert_equal(str(tp(1e10)), ref,
+        ref = '(1e+16+0j)'
+        assert_equal(str(tp(1e16)), ref,
                      err_msg='Failed str formatting for type %s' % tp)
 
 def test_complex_types():
@@ -90,21 +90,21 @@ def test_complex_inf_nan():
     """Check inf/nan formatting of complex types."""
     TESTS = {
         complex(np.inf, 0): "(inf+0j)",
-        complex(0, np.inf): "inf*j",
+        complex(0, np.inf): "infj",
         complex(-np.inf, 0): "(-inf+0j)",
-        complex(0, -np.inf): "-inf*j",
+        complex(0, -np.inf): "-infj",
         complex(np.inf, 1): "(inf+1j)",
-        complex(1, np.inf): "(1+inf*j)",
+        complex(1, np.inf): "(1+infj)",
         complex(-np.inf, 1): "(-inf+1j)",
-        complex(1, -np.inf): "(1-inf*j)",
+        complex(1, -np.inf): "(1-infj)",
         complex(np.nan, 0): "(nan+0j)",
-        complex(0, np.nan): "nan*j",
+        complex(0, np.nan): "nanj",
         complex(-np.nan, 0): "(nan+0j)",
-        complex(0, -np.nan): "nan*j",
+        complex(0, -np.nan): "nanj",
         complex(np.nan, 1): "(nan+1j)",
-        complex(1, np.nan): "(1+nan*j)",
+        complex(1, np.nan): "(1+nanj)",
         complex(-np.nan, 1): "(nan+1j)",
-        complex(1, -np.nan): "(1+nan*j)",
+        complex(1, -np.nan): "(1+nanj)",
     }
     for tp in [np.complex64, np.cdouble, np.clongdouble]:
         for c, s in TESTS.items():
@@ -139,11 +139,11 @@ def check_float_type_print(tp):
     for x in [np.inf, -np.inf, np.nan]:
         _test_redirected_print(float(x), tp, _REF[x])
 
-    if tp(1e10).itemsize > 4:
-        _test_redirected_print(float(1e10), tp)
+    if tp(1e16).itemsize > 4:
+        _test_redirected_print(float(1e16), tp)
     else:
-        ref = '1e+10'
-        _test_redirected_print(float(1e10), tp, ref)
+        ref = '1e+16'
+        _test_redirected_print(float(1e16), tp, ref)
 
 def check_complex_type_print(tp):
     # We do not create complex with inf/nan directly because the feature is
@@ -151,11 +151,11 @@ def check_complex_type_print(tp):
     for x in [0, 1, -1, 1e20]:
         _test_redirected_print(complex(x), tp)
 
-    if tp(1e10).itemsize > 8:
-        _test_redirected_print(complex(1e10), tp)
+    if tp(1e16).itemsize > 8:
+        _test_redirected_print(complex(1e16), tp)
     else:
-        ref = '(1e+10+0j)'
-        _test_redirected_print(complex(1e10), tp, ref)
+        ref = '(1e+16+0j)'
+        _test_redirected_print(complex(1e16), tp, ref)
 
     _test_redirected_print(complex(np.inf, 1), tp, '(inf+1j)')
     _test_redirected_print(complex(-np.inf, 1), tp, '(-inf+1j)')
@@ -240,7 +240,7 @@ def test_locale_double():
 
 @in_foreign_locale
 def test_locale_longdouble():
-    assert_equal(str(np.longdouble(1.2)), str(float(1.2)))
+    assert_equal(str(np.longdouble('1.2')), str(float(1.2)))
 
 if __name__ == "__main__":
     run_module_suite()
diff --git a/numpy/core/tests/test_scalarprint.py b/numpy/core/tests/test_scalarprint.py
--- a/numpy/core/tests/test_scalarprint.py
+++ b/numpy/core/tests/test_scalarprint.py
@@ -5,26 +5,193 @@
 from __future__ import division, absolute_import, print_function
 
 import numpy as np
-from numpy.testing import assert_, run_module_suite
+from numpy.testing import assert_, assert_equal, run_module_suite
 
 
 class TestRealScalars(object):
     def test_str(self):
         svals = [0.0, -0.0, 1, -1, np.inf, -np.inf, np.nan]
         styps = [np.float16, np.float32, np.float64, np.longdouble]
-        actual = [str(f(c)) for c in svals for f in styps]
         wanted = [
-             '0.0',  '0.0',  '0.0',  '0.0',
-             '-0.0', '-0.0', '-0.0', '-0.0',
-             '1.0',  '1.0',  '1.0',  '1.0',
-             '-1.0', '-1.0', '-1.0', '-1.0',
-             'inf',  'inf',  'inf',  'inf',
-             '-inf', '-inf', '-inf', '-inf',
-             'nan',  'nan',  'nan',  'nan']
+             ['0.0',  '0.0',  '0.0',  '0.0' ],
+             ['-0.0', '-0.0', '-0.0', '-0.0'],
+             ['1.0',  '1.0',  '1.0',  '1.0' ],
+             ['-1.0', '-1.0', '-1.0', '-1.0'],
+             ['inf',  'inf',  'inf',  'inf' ],
+             ['-inf', '-inf', '-inf', '-inf'],
+             ['nan',  'nan',  'nan',  'nan']]
 
-        for res, val in zip(actual, wanted):
-            assert_(res == val)
+        for wants, val in zip(wanted, svals):
+            for want, styp in zip(wants, styps):
+                msg = 'for str({}({}))'.format(np.dtype(styp).name, repr(val))
+                assert_equal(str(styp(val)), want, err_msg=msg)
 
+    def test_dragon4(self):
+        # these tests are adapted from Ryan Juckett's dragon4 implementation,
+        # see dragon4.c for details.
+
+        fpos32 = lambda x, **k: np.format_float_positional(np.float32(x), **k)
+        fsci32 = lambda x, **k: np.format_float_scientific(np.float32(x), **k)
+        fpos64 = lambda x, **k: np.format_float_positional(np.float64(x), **k)
+        fsci64 = lambda x, **k: np.format_float_scientific(np.float64(x), **k)
+
+        preckwd = lambda prec: {'unique': False, 'precision': prec}
+
+        assert_equal(fpos32('1.0'), "1.")
+        assert_equal(fsci32('1.0'), "1.e+00")
+        assert_equal(fpos32('10.234'), "10.234")
+        assert_equal(fpos32('-10.234'), "-10.234")
+        assert_equal(fsci32('10.234'), "1.0234e+01")
+        assert_equal(fsci32('-10.234'), "-1.0234e+01")
+        assert_equal(fpos32('1000.0'), "1000.")
+        assert_equal(fpos32('1.0', precision=0), "1.")
+        assert_equal(fsci32('1.0', precision=0), "1.e+00")
+        assert_equal(fpos32('10.234', precision=0), "10.")
+        assert_equal(fpos32('-10.234', precision=0), "-10.")
+        assert_equal(fsci32('10.234', precision=0), "1.e+01")
+        assert_equal(fsci32('-10.234', precision=0), "-1.e+01")
+        assert_equal(fpos32('10.234', precision=2), "10.23")
+        assert_equal(fsci32('-10.234', precision=2), "-1.02e+01")
+        assert_equal(fsci64('9.9999999999999995e-08', **preckwd(16)),
+                            '9.9999999999999995e-08')
+        assert_equal(fsci64('9.8813129168249309e-324', **preckwd(16)),
+                            '9.8813129168249309e-324')
+        assert_equal(fsci64('9.9999999999999694e-311', **preckwd(16)),
+                            '9.9999999999999694e-311')
+
+
+	# test rounding
+        # 3.1415927410 is closest float32 to np.pi
+        assert_equal(fpos32('3.14159265358979323846', **preckwd(10)),
+                            "3.1415927410")
+        assert_equal(fsci32('3.14159265358979323846', **preckwd(10)),
+                            "3.1415927410e+00")
+        assert_equal(fpos64('3.14159265358979323846', **preckwd(10)),
+                            "3.1415926536")
+        assert_equal(fsci64('3.14159265358979323846', **preckwd(10)),
+                            "3.1415926536e+00")
+        # 299792448 is closest float32 to 299792458
+        assert_equal(fpos32('299792458.0', **preckwd(5)), "299792448.00000")
+        assert_equal(fsci32('299792458.0', **preckwd(5)), "2.99792e+08")
+        assert_equal(fpos64('299792458.0', **preckwd(5)), "299792458.00000")
+        assert_equal(fsci64('299792458.0', **preckwd(5)), "2.99792e+08")
+
+        assert_equal(fpos32('3.14159265358979323846', **preckwd(25)),
+                            "3.1415927410125732421875000")
+        assert_equal(fpos64('3.14159265358979323846', **preckwd(50)),
+                         "3.14159265358979311599796346854418516159057617187500")
+        assert_equal(fpos64('3.14159265358979323846'), "3.141592653589793")
+
+
+	# smallest numbers
+        assert_equal(fpos32(0.5**(126 + 23), unique=False, precision=149),
+                    "0.00000000000000000000000000000000000000000000140129846432"
+                    "4817070923729583289916131280261941876515771757068283889791"
+                    "08268586060148663818836212158203125")
+        assert_equal(fpos64(0.5**(1022 + 52), unique=False, precision=1074),
+                    "0.00000000000000000000000000000000000000000000000000000000"
+                    "0000000000000000000000000000000000000000000000000000000000"
+                    "0000000000000000000000000000000000000000000000000000000000"
+                    "0000000000000000000000000000000000000000000000000000000000"
+                    "0000000000000000000000000000000000000000000000000000000000"
+                    "0000000000000000000000000000000000049406564584124654417656"
+                    "8792868221372365059802614324764425585682500675507270208751"
+                    "8652998363616359923797965646954457177309266567103559397963"
+                    "9877479601078187812630071319031140452784581716784898210368"
+                    "8718636056998730723050006387409153564984387312473397273169"
+                    "6151400317153853980741262385655911710266585566867681870395"
+                    "6031062493194527159149245532930545654440112748012970999954"
+                    "1931989409080416563324524757147869014726780159355238611550"
+                    "1348035264934720193790268107107491703332226844753335720832"
+                    "4319360923828934583680601060115061698097530783422773183292"
+                    "4790498252473077637592724787465608477820373446969953364701"
+                    "7972677717585125660551199131504891101451037862738167250955"
+                    "8373897335989936648099411642057026370902792427675445652290"
+                    "87538682506419718265533447265625")
+
+	# largest numbers
+        assert_equal(fpos32(np.finfo(np.float32).max, **preckwd(0)),
+	            "340282346638528859811704183484516925440.")
+        assert_equal(fpos64(np.finfo(np.float64).max, **preckwd(0)),
+                    "1797693134862315708145274237317043567980705675258449965989"
+                    "1747680315726078002853876058955863276687817154045895351438"
+                    "2464234321326889464182768467546703537516986049910576551282"
+                    "0762454900903893289440758685084551339423045832369032229481"
+                    "6580855933212334827479782620414472316873817718091929988125"
+                    "0404026184124858368.")
+        # Warning: In unique mode only the integer digits necessary for
+        # uniqueness are computed, the rest are 0. Should we change this?
+        assert_equal(fpos32(np.finfo(np.float32).max, precision=0),
+	            "340282350000000000000000000000000000000.")
+
+	# test trailing zeros
+        assert_equal(fpos32('1.0', unique=False, precision=3), "1.000")
+        assert_equal(fpos64('1.0', unique=False, precision=3), "1.000")
+        assert_equal(fsci32('1.0', unique=False, precision=3), "1.000e+00")
+        assert_equal(fsci64('1.0', unique=False, precision=3), "1.000e+00")
+        assert_equal(fpos32('1.5', unique=False, precision=3), "1.500")
+        assert_equal(fpos64('1.5', unique=False, precision=3), "1.500")
+        assert_equal(fsci32('1.5', unique=False, precision=3), "1.500e+00")
+        assert_equal(fsci64('1.5', unique=False, precision=3), "1.500e+00")
+
+    def test_dragon4_interface(self):
+        tps = [np.float16, np.float32, np.float64]
+        if hasattr(np, 'float128'):
+            tps.append(np.float128)
+
+        fpos = np.format_float_positional
+        fsci = np.format_float_scientific
+
+        for tp in tps:
+            # test padding
+            assert_equal(fpos(tp('1.0'), pad_left=4, pad_right=4), "   1.    ")
+            assert_equal(fpos(tp('-1.0'), pad_left=4, pad_right=4), "  -1.    ")
+            assert_equal(fpos(tp('-10.2'),
+                         pad_left=4, pad_right=4), " -10.2   ")
+
+            # test exp_digits
+            assert_equal(fsci(tp('1.23e1'), exp_digits=5), "1.23e+00001")
+
+            # test fixed (non-unique) mode
+            assert_equal(fpos(tp('1.0'), unique=False, precision=4), "1.0000")
+            assert_equal(fsci(tp('1.0'), unique=False, precision=4),
+                         "1.0000e+00")
+
+            # test trimming
+            # trim of 'k' or '.' only affects non-unique mode, since unique
+            # mode will not output trailing 0s.
+            assert_equal(fpos(tp('1.'), unique=False, precision=4, trim='k'),
+                         "1.0000")
+
+            assert_equal(fpos(tp('1.'), unique=False, precision=4, trim='.'),
+                         "1.")
+            assert_equal(fpos(tp('1.2'), unique=False, precision=4, trim='.'),
+                         "1.2" if tp != np.float16 else "1.2002")
+
+            assert_equal(fpos(tp('1.'), unique=False, precision=4, trim='0'),
+                         "1.0")
+            assert_equal(fpos(tp('1.2'), unique=False, precision=4, trim='0'),
+                         "1.2" if tp != np.float16 else "1.2002")
+            assert_equal(fpos(tp('1.'), trim='0'), "1.0")
+
+            assert_equal(fpos(tp('1.'), unique=False, precision=4, trim='-'),
+                         "1")
+            assert_equal(fpos(tp('1.2'), unique=False, precision=4, trim='-'),
+                         "1.2" if tp != np.float16 else "1.2002")
+            assert_equal(fpos(tp('1.'), trim='-'), "1")
+
+    def float32_roundtrip(self):
+        # gh-9360
+        x = np.float32(1024 - 2**-14)
+        y = np.float32(1024 - 2**-13)
+        assert_(repr(x) != repr(y))
+        assert_equal(np.float32(repr(x)), x)
+        assert_equal(np.float32(repr(y)), y)
+
+    def float64_vs_python(self):
+        # gh-2643, gh-6136, gh-6908
+        assert_equal(repr(np.float64(0.1)), repr(0.1))
+        assert_(repr(np.float64(0.20000000000000004)) != repr(0.2))
 
 if __name__ == "__main__":
     run_module_suite()