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)