changeset 31878:942ca5f2c71c stable

mean: Process single as double to avoid precision limits on stable (bug #63848). * mean.m: Change all sum calls to process as "double" to avoid single precision limits from producing erroneous output. Update output conversion block to preserve single output for 'default' after forcing double processing and replace expensive call to cast with feval in 'native'. Add BISTs to test calls to sum that could lose precision. Add xfail BIST showing the same problem with large doubles. * NEWS.8.md: Update description of changes to mean.m.
author Nicholas R. Jankowski <jankowski.nicholas@gmail.com>
date Thu, 02 Mar 2023 21:42:42 -0500
parents 715c212605b7
children fc40c78a8421 b8205aa00a05
files etc/NEWS.8.md scripts/statistics/mean.m
diffstat 2 files changed, 24 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS.8.md	Sun Feb 26 19:54:58 2023 -0800
+++ b/etc/NEWS.8.md	Thu Mar 02 21:42:42 2023 -0500
@@ -39,6 +39,9 @@
 - Octave is now compatible with PCRE2 (UTF-8).  PCRE2 is preferred over PCRE
   if both are installed.  Configure with `--without-pcre2` if you prefer Octave
   to use PCRE in this case.
+  
+- `mean` now internally processes data as type double to reduce liklihood of
+hitting overflow or precision limits with other types (bug #63848).
 
 ### Graphical User Interface
 
--- a/scripts/statistics/mean.m	Sun Feb 26 19:54:58 2023 -0800
+++ b/scripts/statistics/mean.m	Thu Mar 02 21:42:42 2023 -0500
@@ -139,7 +139,7 @@
         n -= sum (idx(:));
         x(idx) = 0;
       endif
-      y = sum (x(:), 1) ./ n;
+      y = sum (x(:), 1, "double") ./ n;
     else
       sz = size (x);
       ## Find the first non-singleton dimension.
@@ -150,7 +150,7 @@
         n = sum (! idx, dim);
         x(idx) = 0;
       endif
-      y = sum (x, dim) ./ n;
+      y = sum (x, dim, "double") ./ n;
     endif
 
   else
@@ -169,7 +169,7 @@
         n = sum (! idx, dim);
         x(idx) = 0;
       endif
-      y = sum (x, dim) ./ n;
+      y = sum (x, dim, "double") ./ n;
 
     else
 
@@ -189,7 +189,7 @@
             n -= sum (idx(:));
             x(idx) = 0;
           endif
-          y = sum (x(:), 1) ./ n;
+          y = sum (x(:), 1, "double") ./ n;
 
         ## for 1 dimension left, return column vector
         case 1
@@ -200,7 +200,7 @@
             if (omitnan)
               x_vec = x_vec(! isnan (x_vec));
             endif
-            y(i) = sum (x_vec, 1) ./ numel (x_vec);
+            y(i) = sum (x_vec, 1, "double") ./ numel (x_vec);
           endfor
           y = ipermute (y, [misdim, dim]);
 
@@ -214,7 +214,7 @@
               if (omitnan)
                 x_vec = x_vec(! isnan (x_vec));
               endif
-              y(i,j) = sum (x_vec, 1) ./ numel (x_vec);
+              y(i,j) = sum (x_vec, 1, "double") ./ numel (x_vec);
             endfor
           endfor
           y = ipermute (y, [misdim, dim]);
@@ -230,17 +230,15 @@
   ## Convert output if requested
   switch (outtype)
     case "default"
-      ## do nothing, the operators already do the right thing.
+      if isa(x, "single")
+        y = single (y);
+      endif
     case "double"
       y = double (y);
     case "native"
       if (! islogical (x))
-        y = cast (y, class (x));
+        y = feval (class (x), y);
       endif
-    otherwise
-      ## FIXME: This is unreachable code.  Valid values already
-      ##        checked in input validation.
-      error ("mean: OUTTYPE '%s' not recognized", outtype);
   endswitch
 
 endfunction
@@ -338,6 +336,17 @@
 %! m(2,3) = 15.52301255230125;
 %! assert (mean (x, [3 2], "omitnan"), m, 4e-14);
 
+## Test limits of single precision summation limits on each code path
+%!assert <*63848> (mean (ones (80e6, 1, "single")), 1, eps)
+%!assert <*63848> (mean (ones (80e6, 1, "single"), "all"), 1, eps)
+%!assert <*63848> (mean (ones (80e6, 1, "single"), 1), 1, eps)
+%!assert <*63848> (mean (ones (80e6, 1, "single"), [1 2]), 1, eps)
+%!assert <*63848> (mean (ones (80e6, 1, "single"), [1 3]), 1, eps)
+
+## Test limits of double precision summation
+%!assert <63848> (mean ([flintmax("double"), ones(1, 2^8-1, "double")]), ...
+%!                               35184372088833-1/(2^8), eps(35184372088833))
+
 ## Test input validation
 %!error <Invalid call> mean ()
 %!error <Invalid call> mean (1, 2, 3)