1########################################################################
2##
3## Copyright (C) 1993-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} num2str (@var{x})
28## @deftypefnx {} {} num2str (@var{x}, @var{precision})
29## @deftypefnx {} {} num2str (@var{x}, @var{format})
30## Convert a number (or array) to a string (or a character array).
31##
32## The optional second argument may either give the number of significant
33## digits (@var{precision}) to be used in the output or a format template
34## string (@var{format}) as in @code{sprintf} (@pxref{Formatted Output}).
35## @code{num2str} can also process complex numbers.
36##
37## Examples:
38##
39## @example
40## num2str (123.456)
41##   @result{} 123.456
42##
43## num2str (123.456, 4)
44##   @result{} 123.5
45##
46## s = num2str ([1, 1.34; 3, 3.56], "%5.1f")
47##   @result{} s =
48##        1.0  1.3
49##        3.0  3.6
50## whos s
51##   @result{} Variables in the current scope:
52##         Attr Name        Size                     Bytes  Class
53##         ==== ====        ====                     =====  =====
54##              s           2x8                         16  char
55##      Total is 16 elements using 16 bytes
56##
57## num2str (1.234 + 27.3i)
58##   @result{} 1.234+27.3i
59## @end example
60##
61## The @code{num2str} function is not very flexible.  For better control
62## over the results, use @code{sprintf} (@pxref{Formatted Output}).
63##
64## Programming Notes:
65##
66## For @sc{matlab} compatibility, leading spaces are stripped before returning
67## the string.
68##
69## Integers larger than @code{flintmax} may not be displayed correctly.
70##
71## For complex @var{x}, the format string may only contain one output
72## conversion specification and nothing else.  Otherwise, results will be
73## unpredictable.
74##
75## Any optional @var{format} specified by the programmer is used without
76## modification.  This is in contrast to @sc{matlab} which tampers with the
77## @var{format} based on internal heuristics.
78## @seealso{sprintf, int2str, mat2str}
79## @end deftypefn
80
81function retval = num2str (x, arg)
82
83  if (nargin != 1 && nargin != 2)
84    print_usage ();
85  elseif (! (isnumeric (x) || islogical (x) || ischar (x)))
86    error ("num2str: X must be a numeric, logical, or character array");
87  endif
88
89  if (ischar (x))
90    retval = x;
91  elseif (isempty (x))
92    retval = "";
93  elseif (isreal (x))
94    if (nargin == 2)
95      if (ischar (arg))
96        fmt = arg;
97      elseif (isnumeric (arg) && isscalar (arg) && arg >= 0 && arg == fix (arg))
98        fmt = sprintf ("%%%d.%dg", arg+7, arg);
99      else
100        error ("num2str: PRECISION must be a scalar integer >= 0");
101      endif
102    else
103      if (isnumeric (x))
104        ## Set up a suitable format string while ignoring Inf/NaN entries
105        valid = isfinite (x(:));
106        ndgt = floor (log10 (max (abs (x(valid)))));
107        if (isempty (ndgt) || ndgt == -Inf)
108          ndgt = 0;  # All Inf or all zero array
109        endif
110
111        if (ndgt > 15 || any (x(valid) != fix (x(valid))))
112          ## Floating point input
113          ndgt = max (ndgt + 5, 5);   # Keep at least 5 significant digits
114          ndgt = min (ndgt, 16);      # Cap significant digits at 16
115          fmt = sprintf ("%%%d.%dg", ndgt+7, ndgt);
116        else
117          ## Integer input
118          ndgt += 3;
119          if (any (! valid))
120            ndgt = max (ndgt, 5);     # Allow space for Inf/NaN
121          endif
122          fmt = sprintf ("%%%d.0f", ndgt);
123        endif
124      else
125        ## Logical input
126        fmt = "%3d";
127      endif
128    endif
129    fmt = do_string_escapes (fmt);  # required now that '\n' is interpreted.
130    nd = ndims (x);
131    nc = columns (x) * (nd - 1);    # ND-arrays are expanded in columns
132    x  = permute (x, [2, 3:nd, 1]);
133    if (! (sum (fmt == "%") > 1 || any (strcmp (fmt, {"%s", "%c"}))))
134      fmt = [deblank(repmat (fmt, 1, nc)), "\n"];
135    endif
136    strtmp = sprintf (fmt, x);
137    retval = strtrim (char (ostrsplit (strtmp, "\n", true)));
138  else   # Complex matrix input
139    if (nargin == 2)
140      if (ischar (arg))
141        fmt = [deblank(arg) "%-+" arg(2:end) "i"];
142      elseif (isnumeric (arg) && isscalar (arg) && arg >= 0 && arg == fix (arg))
143        fmt = sprintf ("%%%d.%dg%%-+%d.%dgi", arg+7, arg, arg+7, arg);
144      else
145        error ("num2str: PRECISION must be a scalar integer >= 0");
146      endif
147    else
148      ## Set up a suitable format string while ignoring Inf/NaN entries
149      valid_real = isfinite (real (x(:)));
150      valid_imag = isfinite (imag (x(:)));
151      ndgt = floor (log10 (max (max (abs (real (x(valid_real)))),
152                                max (abs (imag (x(valid_imag)))))));
153      if (isempty (ndgt) || ndgt == -Inf)
154        ndgt = 0;  # All Inf or all zero array
155      endif
156
157      if (any (x(valid_real & valid_imag) != fix (x(valid_real & valid_imag))))
158        ## Floating point input
159        ndgt = max (ndgt + 5, 5);   # Keep at least 5 significant digits
160        ndgt = min (ndgt, 16);      # Cap significant digits at 16
161        fmt = sprintf ("%%%d.%dg%%-+%d.%dgi", ndgt+7, ndgt, ndgt+7, ndgt);
162      else
163        ## Integer input
164        ndgt += 3;
165        ## FIXME: Integers must be masked to show only 16 significant digits
166        ##        See test case for bug #36133 below
167        fmt = sprintf ("%%%d.0f%%-+%d.0fi", ndgt, ndgt);
168      endif
169    endif
170
171    ## Manipulate the complex value to have real values in the odd
172    ## columns and imaginary values in the even columns.
173    nd = ndims (x);
174    nc = columns (x);
175    idx = repmat ({':'}, nd, 1);
176    perm(1:2:2*nc) = 1:nc;
177    perm(2:2:2*nc) = nc + (1:nc);
178    idx{2} = perm;
179    x = horzcat (real (x), imag (x));
180    x = x(idx{:});
181
182    fmt = [deblank(repmat(fmt, 1, nc * (nd - 1))), "\n"];
183    tmp = sprintf (fmt, permute (x, [2, 3:nd, 1]));
184
185    ## Put the "i"'s where they are supposed to be.
186    tmp = regexprep (tmp, " +i\n", "i\n");
187    tmp = regexprep (tmp, "( +)i", "i$1");
188
189    retval = strtrim (char (ostrsplit (tmp(1:end-1), "\n")));
190  endif
191
192endfunction
193
194
195## Basic tests
196%!assert (num2str (123), "123")
197%!assert (num2str (1.23), "1.23")
198%!assert (num2str (123.456, 4), "123.5")
199%!assert (num2str ([1, 1.34; 3, 3.56], "%5.1f"), ["1.0  1.3"; "3.0  3.6"])
200%!assert (num2str (1.234 + 27.3i), "1.234+27.3i")
201%!assert (num2str ([true false true]), "1  0  1")
202
203## Exceptional values
204%!assert (num2str (19440606), "19440606")
205%!assert (num2str (2^33), "8589934592")
206%!assert (num2str (-2^33), "-8589934592")
207%!assert (num2str (2^33+1i), "8589934592+1i")
208%!assert (num2str (-2^33+1i), "-8589934592+1i")
209%!assert (num2str ([0 0 0]), "0  0  0")
210%!assert (num2str (inf), "Inf")
211%!assert (num2str ([inf -inf]), "Inf -Inf")
212%!assert (num2str ([inf NaN -inf]), "Inf  NaN -Inf")
213%!assert (num2str ([complex(Inf,0), complex(0,-Inf)]), "Inf+0i   0-Infi")
214%!assert (num2str (complex(Inf,1)), "Inf+1i")
215%!assert (num2str (complex(1,Inf)), "1+Infi")
216%!assert (num2str (nan), "NaN")
217%!assert (num2str (complex (NaN, 1)), "NaN+1i")
218%!assert (num2str (complex (1, NaN)), "1+NaNi")
219%!assert (num2str (NA), "NA")
220%!assert (num2str (complex (NA, 1)), "NA+1i")
221%!assert (num2str (complex (1, NA)), "1+NAi")
222
223## ND-arrays are concatenated in columns
224%!shared m, x
225%! m = magic (3);
226%! x = cat (3, m, -m);
227
228## real case
229%!test <*46770>
230%! y = num2str (x);
231%! assert (rows (y) == 3);
232%! assert (y, ["8  1  6 -8 -1 -6"
233%!             "3  5  7 -3 -5 -7"
234%!             "4  9  2 -4 -9 -2"]);
235
236## complex case
237%!test <*46770>
238%! x(1,1,2) = -8+2i;
239%! y = num2str (x);
240%! assert (rows (y) == 3);
241%! assert (y, ["8+0i   1+0i   6+0i  -8+2i  -1+0i  -6+0i"
242%!             "3+0i   5+0i   7+0i  -3+0i  -5+0i  -7+0i"
243%!             "4+0i   9+0i   2+0i  -4+0i  -9+0i  -2+0i"]);
244
245## Clear shared variables
246%!shared
247
248## Integers greater than 1e15 should switch to exponential notation
249%!assert <*36133> (num2str (1e15), "1000000000000000")
250%!assert <*36133> (num2str (1e16), "1e+16")
251## Even exact integers in IEEE notation should use exponential notation
252%!assert <*36133> (num2str(2^512), "1.34078079299426e+154")
253## Mixed integer/floating point arrays
254%!assert <*36133> (num2str ([2.1, 1e23, pi]),
255%!                 "2.1  9.999999999999999e+22      3.141592653589793")
256
257## Large integers should not switch sign when printed due to overflow
258%!assert <*36121> (num2str (2.4e9, 15), "2400000000")
259
260## Test for extra rows generated from newlines in format
261%!assert <*44864> (rows (num2str (magic (3), "%3d %3d %3d\n")), 3)
262
263## Test that string conversion of numeric objects results in characters
264## if the numbers are within range for ASCII.
265%!assert <*45174> (num2str ([65 66 67], "%s"), "ABC")
266
267## Test input validation
268%!error num2str ()
269%!error num2str (1, 2, 3)
270%!error <X must be a numeric> num2str ({1})
271%!error <PRECISION must be a scalar integer .= 0> num2str (1, {1})
272%!error <PRECISION must be a scalar integer .= 0> num2str (1, ones (2))
273%!error <PRECISION must be a scalar integer .= 0> num2str (1, -1)
274%!error <PRECISION must be a scalar integer .= 0> num2str (1, 1.5)
275%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, {1})
276%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, ones (2))
277%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, -1)
278%!error <PRECISION must be a scalar integer .= 0> num2str (1+1i, 1.5)
279