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