changeset 33199:297fd823a953

nextpow2.m: Overhaul function for improved accuracy and Matlab compatibility (bug #65441) * nextpow2.m: Clarify documentation. Document return value of special input 0. Add input validation to exclude complex inputs. New algorithm for computing nextpow2 based on 2-output form of log2(). Use cast() to have output class match input class for Matlab compatibility. Add BIST tests. * NEWS.10.md: Announce changes to nextpow2 function.
author Rik <rik@octave.org>
date Tue, 12 Mar 2024 14:27:13 -0700
parents bee755660415
children 9f97974976cd
files etc/NEWS.10.md scripts/general/nextpow2.m
diffstat 2 files changed, 92 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS.10.md	Tue Mar 12 15:05:37 2024 -0400
+++ b/etc/NEWS.10.md	Tue Mar 12 14:27:13 2024 -0700
@@ -17,6 +17,11 @@
 
 - `nchoosek` algorithm is now ~2x faster and provides greater precision.
 
+- `nextpow2` algorithm is now more accurate for inputs very close to a power
+of 2.  The output class now matches the input class for Matlab compatibility.
+The function no longer accepts complex inputs and will emit an error for these
+inputs.
+
 ### Graphical User Interface
 
 ### Graphics backend
--- a/scripts/general/nextpow2.m	Tue Mar 12 15:05:37 2024 -0400
+++ b/scripts/general/nextpow2.m	Tue Mar 12 14:27:13 2024 -0700
@@ -25,16 +25,17 @@
 
 ## -*- texinfo -*-
 ## @deftypefn {} {@var{n} =} nextpow2 (@var{x})
-## Compute the exponent for the smallest power of two larger than the input.
+## Compute the exponent of the next power of two not smaller than the input.
 ##
-## For each element in the input array @var{x}, return the first integer
+## For each element in the input array @var{x}, return the smallest integer
 ## @var{n} such that
 ## @tex
 ## $2^n \ge |x|$.
 ## @end tex
 ## @ifnottex
-## 2^n @geq{} abs (x).
+## @code{2^@var{n} @geq{} abs (@var{x})}.
 ## @end ifnottex
+## For input elements equal to zero, return zero.
 ##
 ## @seealso{pow2, log2}
 ## @end deftypefn
@@ -47,10 +48,23 @@
 
   if (! isnumeric (x))
     error ("nextpow2: X must be numeric");
+  elseif (! isreal (x))
+    error ("nextpow2: X must be real");
   endif
 
-  n = ceil (log2 (abs (x)));
-  n(x == 0) = 0;  # special case
+  [f, n] = log2 (abs (x));
+  n(f == 0.5)--;
+
+  if (isfloat (x))
+    idx_nan = isnan (x);
+    n(idx_nan) = x(idx_nan);
+    n(isinf (x)) = Inf;
+  else
+    n = cast (n, class (x));
+    if (intmax (x) > flintmax ())
+      n((2 .^ n) < abs (x))++;
+    endif
+  endif
 
 endfunction
 
@@ -70,8 +84,75 @@
 %!assert (nextpow2 (Inf), Inf)
 %!assert (nextpow2 (-Inf), Inf)
 %!assert (nextpow2 (NaN), NaN)
-%!assert (nextpow2 ([1, Inf, 3, -Inf, 9, NaN]), [0, Inf, 2, Inf, 4, NaN])
+%!assert (nextpow2 (NA), NA)
+%!assert (nextpow2 ([1, Inf, 3, -Inf, 9, NaN, NA]), ...
+%!                  [0, Inf, 2,  Inf, 4, NaN, NA])
+
+%!test
+%! p = (-1074:1023).';
+%! x = 2 .^ p;
+%! x = [x, x + eps(x)];
+%! x = [x, -x];
+%! n = nextpow2 (x);
+%! assert (n(:, 1), p);
+%! assert (n(:, 3), p);
+%! assert (n(:, 2), p + 1);
+%! assert (n(:, 4), p + 1);
+
+%!assert (nextpow2 (realmax ()), 1024)
+%!assert (nextpow2 (-realmax ()), 1024)
+
+%!test
+%! p = single (-149:127).';
+%! x = 2 .^ p;
+%! x = [x, x + eps(x)];
+%! x = [x, -x];
+%! n = nextpow2 (x);
+%! assert (n(:, 1), p);
+%! assert (n(:, 3), p);
+%! assert (n(:, 2), p + 1);
+%! assert (n(:, 4), p + 1);
+
+%!assert (nextpow2 (realmax ('single')), single (128))
+%!assert (nextpow2 (-realmax ('single')), single (128))
+
+%!test
+%! p = int32 (0:30).';
+%! x = 2 .^ p;
+%! x = [x, x + 1];
+%! x = [x, -x];
+%! n = nextpow2 (x);
+%! assert (n(:, 1), p);
+%! assert (n(:, 3), p);
+%! assert (n(:, 2), p + 1);
+%! assert (n(:, 4), p + 1);
+
+%!assert (nextpow2 (int32 (0)), int32 (0))
+%!assert (nextpow2 (intmin ('int32')), int32 (31))
+%!assert (nextpow2 (intmax ('int32')), int32 (31))
+
+%!assert (nextpow2 (uint32 (0)), uint32 (0))
+%!assert (nextpow2 (intmax ('uint32')), uint32 (32))
+
+%!test
+%! p = int64 (0:62).';
+%! x = 2 .^ p;
+%! x = [x, x + 1];
+%! x = [x, -x];
+%! n = nextpow2 (x);
+%! assert (n(:, 1), p);
+%! assert (n(:, 3), p);
+%! assert (n(:, 2), p + 1);
+%! assert (n(:, 4), p + 1);
+
+%!assert (nextpow2 (int64 (0)), int64 (0))
+%!assert (nextpow2 (intmin ('int64')), int64 (63))
+%!assert (nextpow2 (intmax ('int64')), int64 (63))
+
+%!assert (nextpow2 (uint64 (0)), uint64 (0))
+%!assert (nextpow2 (intmax ('uint64')), uint64 (64))
 
 ## Test input validation
 %!error <Invalid call> nextpow2 ()
 %!error <X must be numeric> nextpow2 ("t")
+%!error <X must be real> nextpow2 (1 + 2i)