From 75bb1a80d104561c7a1870c86e6e5f0972cde5df Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Mon, 15 Mar 2021 08:27:59 -0400 Subject: BUG/ENH: fix array2string rounding bug by adding min_digits option Fixes #18609 --- numpy/core/arrayprint.py | 64 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 20 deletions(-) (limited to 'numpy/core/arrayprint.py') diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 5c1d6cb63..523820c33 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -914,6 +914,7 @@ class FloatingFormat: 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': @@ -927,6 +928,8 @@ class FloatingFormat: 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': @@ -936,10 +939,7 @@ class FloatingFormat: 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 @@ -955,14 +955,14 @@ class FloatingFormat: 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 @@ -991,6 +991,7 @@ class FloatingFormat: 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 == '+', @@ -999,6 +1000,7 @@ class FloatingFormat: else: return dragon4_positional(x, precision=self.precision, + min_digits=self.min_digits, unique=self.unique, fractional=True, trim=self.trim, @@ -1009,7 +1011,8 @@ class FloatingFormat: @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. @@ -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, 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 trim : one of 'k', '.', '0', '-', optional Controls post-processing trimming of trailing digits, as follows: @@ -1048,6 +1052,10 @@ 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. 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. Returns ------- @@ -1071,15 +1079,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. @@ -1097,16 +1108,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: @@ -1123,6 +1137,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. Returns ------- @@ -1147,10 +1165,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: -- cgit v1.2.1 From f14a028404e0457b7ca049fdd4968675cbc9cf54 Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Fri, 19 Mar 2021 12:42:57 -0400 Subject: Update numpy/core/arrayprint.py Co-authored-by: Sebastian Berg --- numpy/core/arrayprint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'numpy/core/arrayprint.py') diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 523820c33..e86986518 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -1053,9 +1053,9 @@ def format_float_scientific(x, precision=None, unique=True, trim='k', 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. 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. + 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. The last additional digit is rounded unbiased. Returns ------- -- cgit v1.2.1 From 5abc3946bd951211df4d0b492bd1781394dddcde Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Fri, 19 Mar 2021 12:45:28 -0400 Subject: Update numpy/core/arrayprint.py Co-authored-by: Sebastian Berg --- numpy/core/arrayprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'numpy/core/arrayprint.py') diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index e86986518..8a93705ca 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -1030,7 +1030,7 @@ 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` - is given fewer digits than necessary can be printed, or if `min_digits` + 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 -- cgit v1.2.1 From d177382065ca135b921175b94a79119c61a1dbd5 Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Fri, 19 Mar 2021 12:58:15 -0400 Subject: Update arrayprint.py --- numpy/core/arrayprint.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'numpy/core/arrayprint.py') diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 8a93705ca..d9d2dbe68 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -1053,9 +1053,10 @@ def format_float_scientific(x, precision=None, unique=True, trim='k', 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. The last additional digit is rounded unbiased. + 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. + Returns ------- -- cgit v1.2.1 From 179d62bc18174378e220c6babf3806b838f02229 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 31 Mar 2021 10:14:34 -0600 Subject: DOC: Add versionadded for new min_digits argument. --- numpy/core/arrayprint.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'numpy/core/arrayprint.py') diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index d9d2dbe68..73d276463 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -1057,7 +1057,8 @@ def format_float_scientific(x, precision=None, unique=True, trim='k', `unique=True`. In that case more digits than necessary to uniquely identify the value may be printed and rounded unbiased. - + -- versionadded:: 1.21.0 + Returns ------- rep : string @@ -1142,6 +1143,8 @@ def format_float_positional(x, precision=None, unique=True, 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. + + -- versionadded:: 1.21.0 Returns ------- -- cgit v1.2.1