Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG, ENH: fix array2string rounding bug by adding min_digits option #18629

Merged
merged 5 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 45 additions & 20 deletions numpy/core/arrayprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ def fillFormat(self, data):
self.trim = '.'
self.exp_size = -1
self.unique = True
self.min_digits = None
elif self.exp_format:
trim, unique = '.', True
if self.floatmode == 'fixed' or self._legacy == '1.13':
Expand All @@ -927,6 +928,8 @@ def fillFormat(self, data):

self.trim = 'k'
self.precision = max(len(s) for s in frac_part)
self.min_digits = self.precision
self.unique = unique

# for back-compat with np 1.13, use 2 spaces & sign and full prec
if self._legacy == '1.13':
Expand All @@ -936,10 +939,7 @@ def fillFormat(self, data):
self.pad_left = max(len(s) for s in int_part)
# pad_right is only needed for nan length calculation
self.pad_right = self.exp_size + 2 + self.precision

self.unique = False
else:
# first pass printing to determine sizes
trim, unique = '.', True
if self.floatmode == 'fixed':
trim, unique = 'k', False
Expand All @@ -955,14 +955,14 @@ def fillFormat(self, data):
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
self.unique = unique

if self.floatmode in ['fixed', 'maxprec_equal']:
self.precision = self.pad_right
self.unique = False
self.precision = self.min_digits = self.pad_right
self.trim = 'k'
else:
self.unique = True
self.trim = '.'
self.min_digits = 0

if self._legacy != '1.13':
# account for sign = ' ' by adding one to pad_left
Expand Down Expand Up @@ -991,6 +991,7 @@ def __call__(self, x):
if self.exp_format:
return dragon4_scientific(x,
precision=self.precision,
min_digits=self.min_digits,
unique=self.unique,
trim=self.trim,
sign=self.sign == '+',
Expand All @@ -999,6 +1000,7 @@ def __call__(self, x):
else:
return dragon4_positional(x,
precision=self.precision,
min_digits=self.min_digits,
unique=self.unique,
fractional=True,
trim=self.trim,
Expand All @@ -1009,7 +1011,8 @@ def __call__(self, x):

@set_module('numpy')
def format_float_scientific(x, precision=None, unique=True, trim='k',
sign=False, pad_left=None, exp_digits=None):
sign=False, pad_left=None, exp_digits=None,
min_digits=None):
"""
Format a floating-point scalar as a decimal string in scientific notation.

Expand All @@ -1027,11 +1030,12 @@ def format_float_scientific(x, precision=None, unique=True, trim='k',
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 all necessary digits, otherwise digit generation is
cut off after `precision` digits and the remaining value is rounded.
is given fewer digits than necessary can be printed. If `min_digits`
is given more can be printed, in which cases the last digit is rounded
with unbiased rounding.
If `False`, digits are generated as if printing an infinite-precision
value and stopping after `precision` digits, rounding the remaining
value.
value with unbiased rounding
trim : one of 'k', '.', '0', '-', optional
Controls post-processing trimming of trailing digits, as follows:

Expand All @@ -1048,6 +1052,11 @@ def format_float_scientific(x, precision=None, unique=True, trim='k',
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.
min_digits : non-negative integer or None, optional
Minimum number of digits to print. This only has an effect for
`unique=True`. In that case more digits than necessary to uniquely
identify the value may be printed and rounded unbiased.

charris marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Expand All @@ -1071,15 +1080,18 @@ def format_float_scientific(x, precision=None, unique=True, trim='k',
precision = _none_or_positive_arg(precision, 'precision')
pad_left = _none_or_positive_arg(pad_left, 'pad_left')
exp_digits = _none_or_positive_arg(exp_digits, 'exp_digits')
min_digits = _none_or_positive_arg(min_digits, 'min_digits')
if min_digits > 0 and precision > 0 and min_digits > precision:
raise ValueError("min_digits must be less than or equal to precision")
return dragon4_scientific(x, precision=precision, unique=unique,
trim=trim, sign=sign, pad_left=pad_left,
exp_digits=exp_digits)
exp_digits=exp_digits, min_digits=min_digits)


@set_module('numpy')
def format_float_positional(x, precision=None, unique=True,
fractional=True, trim='k', sign=False,
pad_left=None, pad_right=None):
pad_left=None, pad_right=None, min_digits=None):
"""
Format a floating-point scalar as a decimal string in positional notation.

Expand All @@ -1097,16 +1109,19 @@ def format_float_positional(x, precision=None, unique=True,
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 all necessary digits, otherwise digit generation
is cut off after `precision` digits and the remaining value is rounded.
is given fewer digits than necessary can be printed, or if `min_digits`
is given more can be printed, in which cases the last digit is rounded
with unbiased rounding.
If `False`, digits are generated as if printing an infinite-precision
value and stopping after `precision` digits, rounding the remaining
value.
value with unbiased rounding
fractional : boolean, optional
If `True`, the cutoff of `precision` digits refers to the total number
of digits after the decimal point, including leading zeros.
If `False`, `precision` refers to the total number of significant
digits, before or after the decimal point, ignoring leading zeros.
If `True`, the cutoffs of `precision` and `min_digits` refer to the
total number of digits after the decimal point, including leading
zeros.
If `False`, `precision` and `min_digits` refer to the total number of
significant digits, before or after the decimal point, ignoring leading
zeros.
trim : one of 'k', '.', '0', '-', optional
Controls post-processing trimming of trailing digits, as follows:

Expand All @@ -1123,6 +1138,10 @@ def format_float_positional(x, precision=None, unique=True,
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.
min_digits : non-negative integer or None, optional
Minimum number of digits to print. Only has an effect if `unique=True`
in which case additional digits past those necessary to uniquely
identify the value may be printed, rounding the last additional digit.
charris marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Expand All @@ -1147,10 +1166,16 @@ def format_float_positional(x, precision=None, unique=True,
precision = _none_or_positive_arg(precision, 'precision')
pad_left = _none_or_positive_arg(pad_left, 'pad_left')
pad_right = _none_or_positive_arg(pad_right, 'pad_right')
min_digits = _none_or_positive_arg(min_digits, 'min_digits')
if not fractional and precision == 0:
raise ValueError("precision must be greater than 0 if "
"fractional=False")
if min_digits > 0 and precision > 0 and min_digits > precision:
raise ValueError("min_digits must be less than or equal to precision")
return dragon4_positional(x, precision=precision, unique=unique,
fractional=fractional, trim=trim,
sign=sign, pad_left=pad_left,
pad_right=pad_right)
pad_right=pad_right, min_digits=min_digits)


class IntegerFormat:
Expand Down
2 changes: 2 additions & 0 deletions numpy/core/arrayprint.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def format_float_scientific(
sign: bool = ...,
pad_left: Optional[int] = ...,
exp_digits: Optional[int] = ...,
min_digits: Optional[int] = ...,
) -> str: ...
def format_float_positional(
x: _FloatLike_co,
Expand All @@ -113,6 +114,7 @@ def format_float_positional(
sign: bool = ...,
pad_left: Optional[int] = ...,
pad_right: Optional[int] = ...,
min_digits: Optional[int] = ...,
) -> str: ...
def array_repr(
arr: ndarray[Any, Any],
Expand Down
Loading