Mercurial > octave
changeset 21057:b0afe1993268
Overhaul num2str.m and int2str.m for Matlab compatibility (bug #46770).
* num2str.m: Add multiple programming notes to docstring. Re-write algorithm
to determine sprintf format to handle all zero arrays, all NaN/Inf arrays.
Rename variable 'dgt' to 'ndgt' for clarity. Don't add an extra formatting
space to handle the minus sign for negative numbers (Matlab compatibility).
Use '%.0f' format for integers rather than '%g' as it displays slightly better.
Rename variable 'tmp' to 'strtmp' for clarity. Rewrite some BIST tests to
match new function behavior.
* int2str.m: Replace code with the same code used for integers in num2str.m
Add Programming Note to docstring.
author | Rik <rik@octave.org> |
---|---|
date | Wed, 13 Jan 2016 12:05:28 -0800 |
parents | d48fdf3a8c0c |
children | 759fcdf3666d |
files | scripts/general/int2str.m scripts/general/num2str.m |
diffstat | 2 files changed, 89 insertions(+), 91 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/general/int2str.m Wed Jan 13 11:04:53 2016 -0800 +++ b/scripts/general/int2str.m Wed Jan 13 12:05:28 2016 -0800 @@ -41,6 +41,12 @@ ## ## This function is not very flexible. For better control over the ## results, use @code{sprintf} (@pxref{Formatted Output}). +## +## Programming Notes: +## +## Non-integers are rounded to integers before display. Only the real part +## of complex numbers is displayed. +## ## @seealso{sprintf, num2str, mat2str} ## @end deftypefn @@ -50,75 +56,53 @@ if (nargin != 1) print_usage (); + elseif (! (isnumeric (n) || islogical (n) || ischar (n))) + error ("int2str: N must be a numeric, logical, or character array"); endif - if (isempty (n)) - retval = ''; + if (ischar (n)) + retval = n; + return; + elseif (isempty (n)) + retval = ""; return; endif n = round (real (n)); - sz = size (n); - nd = ndims (n); - nc = columns (n); - if (nc > 1) - idx = repmat ({':'}, nd, 1); - idx(2) = 1; - ifmt = get_fmt (n(idx{:}), 0); - idx(2) = 2:sz(2); - rfmt = get_fmt (n(idx{:}), 2); - fmt = [ifmt repmat(rfmt,1,nc-1) "\n"]; - else - fmt = [get_fmt(n, 0) "\n"]; + + ## Set up a suitable format string while ignoring Inf/NaN entries + nan_inf = ! isfinite (n(:)); + ndgt = floor (log10 (max (abs (n(! nan_inf))))); + if (isempty (ndgt) || ndgt == -Inf) + ndgt = 0; # All Inf or all zero array endif - tmp = sprintf (fmt, permute (n, [2, 1, 3 : nd])); - tmp(end) = ""; - retval = char (ostrsplit (tmp, "\n")); - -endfunction - -function fmt = get_fmt (x, sep) - t = x(:); - t = t(t != 0); - if (isempty (t)) - ## All zeros. - fmt = sprintf ("%%%dd", 1 + sep); - else - ## Maybe have some zeros. - nan_inf = ! isfinite (t); - if (any (nan_inf)) - if (any (t(nan_inf) < 0)) - min_fw = 4 + sep; - else - min_fw = 3 + sep; - endif - else - min_fw = 1 + sep; - endif - t = t(! nan_inf); - if (isempty (t)) - ## Only zeros, Inf, and NaN. - fmt = sprintf ("%%%dd", min_fw); - else - ## Could have anything. - tfw = floor (log10 (double (abs (t)))) + 1 + sep; - fw = max (tfw); - if (any (t(tfw == fw) < 0)) - fw += 1; - endif - fmt = sprintf ("%%%dd", max (fw, min_fw)); - endif + ndgt += 3; + if (any (nan_inf)) + ndgt = max (ndgt, 5); endif + ## FIXME: Integers should be masked to show only 16 significant digits + fmt = sprintf ("%%%d.0f", ndgt); + + nd = ndims (n); + nc = columns (n) * (nd - 1); # ND-arrays are expanded in columns + n = permute (n, [2, 3:nd, 1]); + fmt = [repmat(fmt, 1, nc), "\n"]; + strtmp = sprintf (fmt, n); + retval = strtrim (char (ostrsplit (strtmp, "\n", true))); + endfunction +%!assert (int2str (123), "123") %!assert (int2str (-123), "-123") %!assert (int2str (1.2), "1") +%!assert (int2str (1.6), "2") %!assert (int2str ([1, 2, 3; 4, 5, 6]), ["1 2 3";"4 5 6"]) %!assert (int2str ([]), "") %!error int2str () %!error int2str (1, 2) +%!error <N must be a numeric> int2str ({1})
--- a/scripts/general/num2str.m Wed Jan 13 11:04:53 2016 -0800 +++ b/scripts/general/num2str.m Wed Jan 13 12:05:28 2016 -0800 @@ -52,17 +52,23 @@ ## @end group ## @end example ## -## Notes: +## The @code{num2str} function is not very flexible. For better control +## over the results, use @code{sprintf} (@pxref{Formatted Output}). +## +## Programming Notes: ## ## For @sc{matlab} compatibility, leading spaces are stripped before returning ## the string. ## -## The @code{num2str} function is not very flexible. For better control -## over the results, use @code{sprintf} (@pxref{Formatted Output}). +## Integers larger than @code{flintmax} may not be displayed correctly. ## ## For complex @var{x}, the format string may only contain one output ## conversion specification and nothing else. Otherwise, results will be ## unpredictable. +## +## Any optional @var{format} specified by the programmer is used without +## modification. This is in contrast to @sc{matlab} which tampers with the +## @var{format} based on internal heuristics. ## @seealso{sprintf, int2str, mat2str} ## @end deftypefn @@ -91,24 +97,27 @@ endif else if (isnumeric (x)) - ## Setup a suitable format string, ignoring inf entries - dgt = floor (log10 (max (abs (x(! isinf (x(:))))))); - if (isempty (dgt)) - ## If the whole input array is inf... - dgt = 1; + ## Set up a suitable format string while ignoring Inf/NaN entries + valid = isfinite (x(:)); + ndgt = floor (log10 (max (abs (x(valid))))); + if (isempty (ndgt) || ndgt == -Inf) + ndgt = 0; # All Inf or all zero array endif - if (any (x(:) != fix (x(:)))) + if (any (x(valid) != fix (x(valid)))) ## Floating point input - dgt = max (dgt + 4, 5); # Keep 4 sig. figures after decimal point - dgt = min (dgt, 16); # Cap significant digits at 16 - fmt = sprintf ("%%%d.%dg", dgt+7+any (x(:) < 0), dgt); + ndgt = max (ndgt + 5, 5); # Keep at least 5 significant digits + ndgt = min (ndgt, 16); # Cap significant digits at 16 + fmt = sprintf ("%%%d.%dg", ndgt+7, ndgt); else ## Integer input - dgt = max (dgt + 1, 1); + ndgt += 3; + if (any (! valid)) + ndgt = max (ndgt, 5); # Allow space for Inf/NaN + endif ## FIXME: Integers should be masked to show only 16 significant digits - ## See %!xtest below - fmt = sprintf ("%%%d.%dg", dgt+2+any (x(:) < 0), dgt); + ## See %!xtest with 1e23 below. + fmt = sprintf ("%%%d.0f", ndgt); endif else ## Logical input @@ -122,8 +131,8 @@ if (! (sum (fmt == "%") > 1 || any (strcmp (fmt, {"%s", "%c"})))) fmt = [deblank(repmat (fmt, 1, nc)), "\n"]; endif - tmp = sprintf (fmt, x); - retval = strtrim (char (ostrsplit (tmp, "\n", true))); + strtmp = sprintf (fmt, x); + retval = strtrim (char (ostrsplit (strtmp, "\n", true))); else # Complex matrix input if (nargin == 2) if (ischar (arg)) @@ -134,25 +143,26 @@ error ("num2str: PRECISION must be a scalar integer >= 0"); endif else - ## Setup a suitable format string - dgt = floor (log10 (max (max (abs (real (x(! isinf (real (x(:))))))), - max (abs (imag (x(! isinf (imag (x(:)))))))))); - if (isempty (dgt)) - ## If the whole input array is inf... - dgt = 1; + ## Set up a suitable format string while ignoring Inf/NaN entries + valid_real = isfinite (real (x(:))); + valid_imag = isfinite (imag (x(:))); + ndgt = floor (log10 (max (max (abs (real (x(valid_real)))), + max (abs (imag (x(valid_imag))))))); + if (isempty (ndgt) || ndgt == -Inf) + ndgt = 0; # All Inf or all zero array endif - if (any (x(:) != fix (x(:)))) + if (any (x(valid_real & valid_imag) != fix (x(valid_real & valid_imag)))) ## Floating point input - dgt = max (dgt + 4, 5); # Keep 4 sig. figures after decimal point - dgt = min (dgt, 16); # Cap significant digits at 16 - fmt = sprintf ("%%%d.%dg%%-+%d.%dgi", dgt+7, dgt, dgt+7, dgt); + ndgt = max (ndgt + 5, 5); # Keep at least 5 significant digits + ndgt = min (ndgt, 16); # Cap significant digits at 16 + fmt = sprintf ("%%%d.%dg%%-+%d.%dgi", ndgt+7, ndgt, ndgt+7, ndgt); else ## Integer input - dgt = max (1 + dgt, 1); + ndgt += 3; ## FIXME: Integers should be masked to show only 16 significant digits ## See %!xtest below - fmt = sprintf ("%%%d.%dg%%-+%d.%dgi", dgt+2, dgt, dgt+2, dgt); + fmt = sprintf ("%%%d.0f%%-+%d.0fi", ndgt, ndgt); endif endif @@ -192,8 +202,10 @@ %!assert (num2str (-2^33), "-8589934592") %!assert (num2str (2^33+1i), "8589934592+1i") %!assert (num2str (-2^33+1i), "-8589934592+1i") +%!assert (num2str ([0 0 0]), "0 0 0") %!assert (num2str (inf), "Inf") %!assert (num2str ([inf -inf]), "Inf -Inf") +%!assert (num2str ([inf NaN -inf]), "Inf NaN -Inf") %!assert (num2str ([complex(Inf,0), complex(0,-Inf)]), "Inf+0i 0-Infi") %!assert (num2str (complex(Inf,1)), "Inf+1i") %!assert (num2str (complex(1,Inf)), "1+Infi") @@ -213,9 +225,9 @@ %!test %! y = num2str (x); %! assert (rows (y) == 3); -%! assert (y, ["8 1 6 -8 -1 -6" -%! "3 5 7 -3 -5 -7" -%! "4 9 2 -4 -9 -2"]); +%! assert (y, ["8 1 6 -8 -1 -6" +%! "3 5 7 -3 -5 -7" +%! "4 9 2 -4 -9 -2"]); ## complex case %!test @@ -235,18 +247,20 @@ %! assert (num2str (1e23), "100000000000000000000000"); ## Test for bug #44864, extra rows generated from newlines in format -%!assert (rows (num2str (magic (3), '%3d %3d %3d\n')), 3) +%!assert (rows (num2str (magic (3), "%3d %3d %3d\n")), 3) ## Test for bug #45174 -%!assert (num2str ([65 66 67], '%s'), "ABC") +%!assert (num2str ([65 66 67], "%s"), "ABC") %!error num2str () %!error num2str (1, 2, 3) %!error <X must be a numeric> num2str ({1}) -%!error <PRECISION must be a scalar integer> num2str (1, {1}) -%!error <PRECISION must be a scalar integer> num2str (1, ones (2)) -%!error <PRECISION must be a scalar integer> num2str (1, -1) -%!error <PRECISION must be a scalar integer> num2str (1+1i, {1}) -%!error <PRECISION must be a scalar integer> num2str (1+1i, ones (2)) -%!error <PRECISION must be a scalar integer> num2str (1+1i, -1) +%!error <PRECISION must be a scalar integer .= 0> num2str (1, {1}) +%!error <PRECISION must be a scalar integer .= 0> num2str (1, ones (2)) +%!error <PRECISION must be a scalar integer .= 0> num2str (1, -1) +%!error <PRECISION must be a scalar integer .= 0> num2str (1, 1.5) +%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, {1}) +%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, ones (2)) +%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, -1) +%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, 1.5)