# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1638292760 -3600
#      Tue Nov 30 18:19:20 2021 +0100
# Branch stable
# Node ID f03166ed5fe18765bbe684d65fa08354396768dd
# Parent  597275db9c7fb46de6381c876c3c81d60e6e9c49
Allow descending ranges of all integer types (bug #61132).

* liboctave/array/Range.h (range<T>): Add new property "m_reverse" for reverse
ranges. Add optional argument "reverse" to constructors. Add support for reverse
ranges to member functions.
* liboctave/array/Range.cc (xinit<T>): Add option for reverse ranges.
* libinterp/octave-value/ov.cc (make_range): Create reverse ranges for negative
increments.
* libinterp/octave-value/ov-range.cc (save_ascii, load_ascii, save_binary,
load_binary, save_hdf5, load_hdf5): Include config.h to allow saving as HDF5.
Add support for saving and loading reverse ranges. Add tests.
* test/range.tst: Add tests for descending ranges with unsigned integers.

diff -r 597275db9c7f -r f03166ed5fe1 libinterp/octave-value/ov-range.cc
--- a/libinterp/octave-value/ov-range.cc	Tue Nov 30 11:04:27 2021 -0500
+++ b/libinterp/octave-value/ov-range.cc	Tue Nov 30 18:19:20 2021 +0100
@@ -23,9 +23,9 @@
 //
 ////////////////////////////////////////////////////////////////////////
 
-// This file should not include config.h.  It is only included in other
-// C++ source files that should have included config.h before including
-// this file.
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
 
 #include <istream>
 #include <ostream>
@@ -648,18 +648,24 @@
 
 template <typename T>
 bool
-ov_range<T>::save_ascii (std::ostream& os)
+xsave_ascii (std::ostream& os, const octave::range<T>& r,
+             const bool with_reverse)
 {
-  octave::range<T> r = m_range;
   T base = r.base ();
   T limit = r.limit ();
   T inc = r.increment ();
+  bool rev = r.reverse ();
   octave_idx_type len = r.numel ();
 
   if (inc != T (0))
-    os << "# base, limit, increment\n";
+    os << "# base, limit, increment";
   else
-    os << "# base, length, increment\n";
+    os << "# base, length, increment";
+
+  if (with_reverse)
+    os << ", reverse\n";
+  else
+    os << "\n";
 
   octave::write_value<T> (os, base);
   os << ' ';
@@ -669,6 +675,8 @@
     os << len;
   os << ' ';
   octave::write_value<T> (os, inc);
+  if (with_reverse)
+    os << ' ' << rev;
   os << "\n";
 
   return true;
@@ -676,37 +684,141 @@
 
 template <typename T>
 bool
+ov_range<T>::save_ascii (std::ostream& os)
+{
+  return xsave_ascii (os, m_range, false);
+}
+
+// specialize for saving with "reverse" flag
+
+template <>
+bool
+ov_range<octave_uint8>::save_ascii (std::ostream& os)
+{
+  return xsave_ascii (os, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint16>::save_ascii (std::ostream& os)
+{
+  return xsave_ascii (os, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint32>::save_ascii (std::ostream& os)
+{
+  return xsave_ascii (os, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint64>::save_ascii (std::ostream& os)
+{
+  return xsave_ascii (os, m_range, true);
+}
+
+template <typename T>
+bool
+xload_ascii (std::istream& is, octave::range<T>& r, const bool with_reverse)
+{
+  // # base, limit, range comment added by save ().
+  skip_comments (is);
+
+  T base, limit, inc;
+  bool rev = false;
+  is >> base >> limit >> inc;
+
+  if (with_reverse)
+    is >> rev;
+
+  if (! is)
+    error ("load: failed to load range constant");
+
+  if (inc != T (0))
+    r = octave::range<T> (base, inc, limit, rev);
+  else
+    {
+      octave_idx_type numel = static_cast<octave_idx_type> (limit);
+      r = octave::range<T>::make_constant (base, numel, rev);
+    }
+
+  return true;
+}
+
+template <typename T>
+bool
 ov_range<T>::load_ascii (std::istream& is)
 {
-  // # base, limit, range comment added by save ().
-  skip_comments (is);
+  return xload_ascii (is, m_range, false);
+}
+
+// specialize for loading with "reverse" flag
+
+template <>
+bool
+ov_range<octave_uint8>::load_ascii (std::istream& is)
+{
+  return xload_ascii (is, m_range, true);
+}
 
-  T base, limit, inc;
-  is >> base >> limit >> inc;
+template <>
+bool
+ov_range<octave_uint16>::load_ascii (std::istream& is)
+{
+  return xload_ascii (is, m_range, true);
+}
 
-  if (! is)
-    error ("load: failed to load range constant");
+template <>
+bool
+ov_range<octave_uint32>::load_ascii (std::istream& is)
+{
+  return xload_ascii (is, m_range, true);
+}
 
-  if (inc != T (0))
-    m_range = octave::range<T> (base, limit, inc);
-  else
-    {
-      octave_idx_type numel = static_cast<octave_idx_type> (limit);
-      m_range = octave::range<T>::make_constant (base, numel);
-    }
+template <>
+bool
+ov_range<octave_uint64>::load_ascii (std::istream& is)
+{
+  return xload_ascii (is, m_range, true);
+}
 
-  return true;
-}
+/*
+%!test
+%! a = b = 1:4;
+%! sv_file = [tempname(), ".sav"];
+%! unwind_protect
+%!   save (sv_file, "a", "-text");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+
+%!test
+%! a = b = uint8(5):-1:0;
+%! sv_file = [tempname(), ".sav"];
+%! unwind_protect
+%!   save (sv_file, "a", "-text");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+*/
 
 template <typename T>
 bool
-ov_range<T>::save_binary (std::ostream& os, bool /* save_as_floats */)
+xsave_binary (std::ostream& os, bool /* save_as_floats */,
+              const octave::range<T>& r, const bool with_reverse)
 {
   // FIXME: Not always double!
 
   char tmp = LS_DOUBLE;
   os.write (reinterpret_cast<char *> (&tmp), 1);
-  octave::range<T> r = m_range;
   T bas = r.base ();
   T lim = r.limit ();
   T inc = r.increment ();
@@ -716,14 +828,55 @@
   os.write (reinterpret_cast<char *> (&bas), sizeof (T));
   os.write (reinterpret_cast<char *> (&lim), sizeof (T));
   os.write (reinterpret_cast<char *> (&inc), sizeof (T));
+  if (with_reverse)
+    {
+      bool rev = r.reverse ();
+      os.write (reinterpret_cast<char *> (&rev), sizeof (bool));
+    }
 
   return true;
 }
 
 template <typename T>
 bool
-ov_range<T>::load_binary (std::istream& is, bool swap,
-                          octave::mach_info::float_format /* fmt */)
+ov_range<T>::save_binary (std::ostream& os, bool save_as_floats)
+{
+  return xsave_binary (os, save_as_floats, m_range, false);
+}
+
+template <>
+bool
+ov_range<octave_uint8>::save_binary (std::ostream& os, bool save_as_floats)
+{
+  return xsave_binary (os, save_as_floats, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint16>::save_binary (std::ostream& os, bool save_as_floats)
+{
+  return xsave_binary (os, save_as_floats, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint32>::save_binary (std::ostream& os, bool save_as_floats)
+{
+  return xsave_binary (os, save_as_floats, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint64>::save_binary (std::ostream& os, bool save_as_floats)
+{
+  return xsave_binary (os, save_as_floats, m_range, true);
+}
+
+template <typename T>
+bool
+xload_binary (std::istream& is, bool swap,
+              octave::mach_info::float_format /* fmt */,
+              octave::range<T>& r, const bool with_reverse)
 {
   // FIXME: Not always double!
 
@@ -743,17 +896,91 @@
     return false;
   if (swap)
     swap_bytes<sizeof (T)> (&inc);
+  bool rev = false;
+  if (with_reverse)
+    {
+      if (! is.read (reinterpret_cast<char *> (&rev), sizeof (bool)))
+        return false;
+      if (swap)
+        swap_bytes<sizeof (bool)> (&rev);
+    }
   if (inc != T (0))
-    m_range = octave::range<T> (bas, inc, lim);
+    r = octave::range<T> (bas, inc, lim, rev);
   else
     {
       octave_idx_type numel = static_cast<octave_idx_type> (lim);
-      m_range = octave::range<T>::make_constant (bas, numel);
+      r = octave::range<T>::make_constant (bas, numel, rev);
     }
 
   return true;
 }
 
+template <typename T>
+bool
+ov_range<T>::load_binary (std::istream& is, bool swap,
+                          octave::mach_info::float_format fmt)
+{
+  return xload_binary (is, swap, fmt, m_range, false);
+}
+
+template <>
+bool
+ov_range<octave_uint8>::load_binary (std::istream& is, bool swap,
+                                     octave::mach_info::float_format fmt)
+{
+  return xload_binary (is, swap, fmt, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint16>::load_binary (std::istream& is, bool swap,
+                                      octave::mach_info::float_format fmt)
+{
+  return xload_binary (is, swap, fmt, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint32>::load_binary (std::istream& is, bool swap,
+                                      octave::mach_info::float_format fmt)
+{
+  return xload_binary (is, swap, fmt, m_range, true);
+}
+
+template <>
+bool
+ov_range<octave_uint64>::load_binary (std::istream& is, bool swap,
+                                      octave::mach_info::float_format fmt)
+{
+  return xload_binary (is, swap, fmt, m_range, true);
+}
+
+/*
+%!test
+%! a = b = 1:4;
+%! sv_file = [tempname(), ".dat"];
+%! unwind_protect
+%!   save (sv_file, "a", "-binary");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+
+%!test
+%! a = b = uint8(5):-1:0;
+%! sv_file = [tempname(), ".dat"];
+%! unwind_protect
+%!   save (sv_file, "a", "-binary");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+*/
+
 #if defined (HAVE_HDF5)
 
 // The following subroutines creates an HDF5 representation of the way
@@ -775,36 +1002,50 @@
   return type_id;
 }
 
-#endif
+template <typename T>
+static hid_t
+hdf5_make_range_rev_type (hid_t num_type)
+{
+  hid_t type_id = H5Tcreate (H5T_COMPOUND, sizeof (T) * 4);
+
+  H5Tinsert (type_id, "base", 0 * sizeof (T), num_type);
+  H5Tinsert (type_id, "limit", 1 * sizeof (T), num_type);
+  H5Tinsert (type_id, "increment", 2 * sizeof (T), num_type);
+  // FIXME: Storing "reverse" with the same width is inefficient.
+  H5Tinsert (type_id, "reverse", 3 * sizeof (T), num_type);
+
+  return type_id;
+}
 
 template <typename T>
 bool
-ov_range<T>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
-                        bool /* save_as_floats */)
+xsave_hdf5 (octave_hdf5_id loc_id, const char *name,
+            bool /* save_as_floats */, const octave::range<T>& r,
+            const octave_hdf5_id h5_save_type, const bool with_reverse)
 {
   bool retval = false;
 
-#if defined (HAVE_HDF5)
-
-  hsize_t dimens[3];
+  hsize_t dimens[3] = {0};
   hid_t space_hid, type_hid, data_hid;
   space_hid = type_hid = data_hid = -1;
 
   space_hid = H5Screate_simple (0, dimens, nullptr);
   if (space_hid < 0) return false;
 
-  type_hid = hdf5_make_range_type<T> (hdf5_save_type);
+  type_hid = with_reverse
+             ? hdf5_make_range_rev_type<T> (h5_save_type)
+             : hdf5_make_range_type<T> (h5_save_type);
   if (type_hid < 0)
     {
       H5Sclose (space_hid);
       return false;
     }
-#if defined (HAVE_HDF5_18)
+#  if defined (HAVE_HDF5_18)
   data_hid = H5Dcreate (loc_id, name, type_hid, space_hid,
                         octave_H5P_DEFAULT, octave_H5P_DEFAULT, octave_H5P_DEFAULT);
-#else
+#  else
   data_hid = H5Dcreate (loc_id, name, type_hid, space_hid, octave_H5P_DEFAULT);
-#endif
+#  endif
   if (data_hid < 0)
     {
       H5Sclose (space_hid);
@@ -812,11 +1053,14 @@
       return false;
     }
 
-  octave::range<T> r = m_range;
-  T range_vals[3];
+  T range_vals[4];
   range_vals[0] = r.base ();
-  range_vals[1] = (r.increment () != T (0) ? r.limit () : r.numel ());
+  if (r.increment () != T (0))
+    range_vals[1] = r.limit ();
+  else
+    range_vals[1] = r.numel ();
   range_vals[2] = r.increment ();
+  range_vals[3] = r.reverse ();
 
   if (H5Dwrite (data_hid, type_hid, octave_H5S_ALL, octave_H5S_ALL,
                 octave_H5P_DEFAULT, range_vals)
@@ -833,32 +1077,124 @@
   H5Tclose (type_hid);
   H5Sclose (space_hid);
 
+  return retval;
+}
+
+#endif
+
+template <typename T>
+bool
+ov_range<T>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                        bool save_as_floats)
+{
+#if defined (HAVE_HDF5)
+  return xsave_hdf5 (loc_id, name, save_as_floats, m_range, hdf5_save_type,
+                     false);
 #else
   octave_unused_parameter (loc_id);
   octave_unused_parameter (name);
 
   warn_save ("hdf5");
+
+  return false;
 #endif
+}
+
+template <>
+bool
+ov_range<octave_uint8>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                                   bool save_as_floats)
+{
+#if defined (HAVE_HDF5)
+  return xsave_hdf5 (loc_id, name, save_as_floats, m_range, hdf5_save_type,
+                     true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+  octave_unused_parameter (save_as_floats);
+
+  warn_save ("hdf5");
+
+  return false;
+#endif
+}
+
+template <>
+bool
+ov_range<octave_uint16>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                                    bool save_as_floats)
+{
+#if defined (HAVE_HDF5)
+  return xsave_hdf5 (loc_id, name, save_as_floats, m_range, hdf5_save_type,
+                     true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+  octave_unused_parameter (save_as_floats);
+
+  warn_save ("hdf5");
 
-  return retval;
+  return false;
+#endif
+}
+
+template <>
+bool
+ov_range<octave_uint32>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                                    bool save_as_floats)
+{
+#if defined (HAVE_HDF5)
+  return xsave_hdf5 (loc_id, name, save_as_floats, m_range, hdf5_save_type,
+                     true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+  octave_unused_parameter (save_as_floats);
+
+  warn_save ("hdf5");
+
+  return false;
+#endif
 }
 
+template <>
+bool
+ov_range<octave_uint64>::save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                                    bool save_as_floats)
+{
+#if defined (HAVE_HDF5)
+  return xsave_hdf5 (loc_id, name, save_as_floats, m_range, hdf5_save_type,
+                     true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+  octave_unused_parameter (save_as_floats);
+
+  warn_save ("hdf5");
+
+  return false;
+#endif
+}
+
+#if defined (HAVE_HDF5)
+
 template <typename T>
 bool
-ov_range<T>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+xload_hdf5 (octave_hdf5_id loc_id, const char *name, octave::range<T>& r,
+            const octave_hdf5_id h5_save_type, const bool with_reverse)
 {
   bool retval = false;
 
-#if defined (HAVE_HDF5)
-
-#if defined (HAVE_HDF5_18)
+#  if defined (HAVE_HDF5_18)
   hid_t data_hid = H5Dopen (loc_id, name, octave_H5P_DEFAULT);
-#else
+#  else
   hid_t data_hid = H5Dopen (loc_id, name);
-#endif
+#  endif
   hid_t type_hid = H5Dget_type (data_hid);
 
-  hid_t range_type = hdf5_make_range_type<T> (hdf5_save_type);
+  hid_t range_type = with_reverse
+                     ? hdf5_make_range_rev_type<T> (h5_save_type)
+                     : hdf5_make_range_type<T> (h5_save_type);
 
   if (! hdf5_types_compatible (type_hid, range_type))
     {
@@ -878,7 +1214,7 @@
       return false;
     }
 
-  T rangevals[3];
+  T rangevals[4];
   if (H5Dread (data_hid, range_type, octave_H5S_ALL, octave_H5S_ALL,
                octave_H5P_DEFAULT, rangevals)
       >= 0)
@@ -887,12 +1223,14 @@
 
       // Don't use OCTAVE_RANGE_NELEM attribute, just reconstruct the range.
 
+      bool rev = with_reverse ? static_cast<bool> (rangevals[3]) : false;
+
       if (rangevals[2] != T (0))
-        m_range = octave::range<T> (rangevals[0], rangevals[1], rangevals[2]);
+        r = octave::range<T> (rangevals[0], rangevals[2], rangevals[1], rev);
       else
         {
           octave_idx_type numel = static_cast<octave_idx_type> (rangevals[1]);
-          m_range = octave::range<T>::make_constant (rangevals[0], numel);
+          r = octave::range<T>::make_constant (rangevals[0], numel, rev);
         }
     }
 
@@ -900,16 +1238,117 @@
   H5Sclose (space_hid);
   H5Dclose (data_hid);
 
+  return retval;
+}
+
+#endif
+
+template <typename T>
+bool
+ov_range<T>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+{
+#if defined (HAVE_HDF5)
+  return xload_hdf5 (loc_id, name, m_range, hdf5_save_type, false);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+
+  warn_load ("hdf5");
+
+  return false;
+#endif
+}
+
+template <>
+bool
+ov_range<octave_uint8>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+{
+#if defined (HAVE_HDF5)
+  return xload_hdf5 (loc_id, name, m_range, hdf5_save_type, true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+
+  warn_load ("hdf5");
+
+  return false;
+#endif
+}
+
+template <>
+bool
+ov_range<octave_uint16>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+{
+#if defined (HAVE_HDF5)
+  return xload_hdf5 (loc_id, name, m_range, hdf5_save_type, true);
 #else
   octave_unused_parameter (loc_id);
   octave_unused_parameter (name);
 
   warn_load ("hdf5");
+
+  return false;
 #endif
+}
+
+template <>
+bool
+ov_range<octave_uint32>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+{
+#if defined (HAVE_HDF5)
+  return xload_hdf5 (loc_id, name, m_range, hdf5_save_type, true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
+
+  warn_load ("hdf5");
+
+  return false;
+#endif
+}
+
+template <>
+bool
+ov_range<octave_uint64>::load_hdf5 (octave_hdf5_id loc_id, const char *name)
+{
+#if defined (HAVE_HDF5)
+  return xload_hdf5 (loc_id, name, m_range, hdf5_save_type, true);
+#else
+  octave_unused_parameter (loc_id);
+  octave_unused_parameter (name);
 
-  return retval;
+  warn_load ("hdf5");
+
+  return false;
+#endif
 }
 
+/*
+%!testif HAVE_HDF5
+%! a = b = 1:4;
+%! sv_file = [tempname(), ".h5"];
+%! unwind_protect
+%!   save (sv_file, "a", "-hdf5");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+
+%!testif HAVE_HDF5
+%! a = b = uint8(5):-1:0;
+%! sv_file = [tempname(), ".h5"];
+%! unwind_protect
+%!   save (sv_file, "a", "-hdf5");
+%!   clear a;
+%!   load (sv_file);
+%!   assert (a, b);
+%! unwind_protect_cleanup
+%!   unlink (sv_file);
+%! end_unwind_protect
+*/
+
 template <typename T>
 mxArray *
 ov_range<T>::as_mxArray (bool interleaved) const
diff -r 597275db9c7f -r f03166ed5fe1 libinterp/octave-value/ov.cc
--- a/libinterp/octave-value/ov.cc	Tue Nov 30 11:04:27 2021 -0500
+++ b/libinterp/octave-value/ov.cc	Tue Nov 30 18:19:20 2021 +0100
@@ -3031,17 +3031,24 @@
     if (base.isempty () || increment.isempty () || limit.isempty ())
       return octave_value (range<T> (), for_cmd_expr);
 
+    bool reverse = (base.is_uint8_type () || base.is_uint16_type ()
+                    || base.is_uint32_type () || base.is_uint64_type ()
+                    || limit.is_uint8_type () || limit.is_uint16_type ()
+                    || limit.is_uint32_type () || limit.is_uint64_type ())
+                   && increment.scalar_value () < 0;
+
     check_colon_operand<T> (base, "lower bound");
-    check_colon_operand<T> (increment, "increment");
+    check_colon_operand<T> ((reverse ? -increment : increment), "increment");
     check_colon_operand<T> (limit, "upper bound");
 
     T base_val = octave_value_extract<T> (base);
 
-    T increment_val = octave_value_extract<T> (increment);
+    T increment_val = octave_value_extract<T> (reverse ? -increment
+                                                       : increment);
 
     T limit_val = octave_value_extract<T> (limit);
 
-    range<T> r (base_val, increment_val, limit_val);
+    range<T> r (base_val, increment_val, limit_val, reverse);
 
     return octave_value (r, for_cmd_expr);
   }
diff -r 597275db9c7f -r f03166ed5fe1 liboctave/array/Range.cc
--- a/liboctave/array/Range.cc	Tue Nov 30 11:04:27 2021 -0500
+++ b/liboctave/array/Range.cc	Tue Nov 30 18:19:20 2021 +0100
@@ -192,7 +192,8 @@
 
   template <typename T>
   void
-  xinit (T base, T limit, T inc, T& final_val, octave_idx_type& nel)
+  xinit (T base, T limit, T inc, const bool reverse, T& final_val,
+         octave_idx_type& nel)
   {
     // Catch obvious NaN ranges.
     if (math::isnan (base) || math::isnan (limit) || math::isnan (inc))
@@ -202,6 +203,10 @@
         return;
       }
 
+    // Floating point numbers are always signed
+    if (reverse)
+      inc = -inc;
+
     // Catch empty ranges.
     if (inc == 0
         || (limit < base && inc > 0)
@@ -254,9 +259,9 @@
 
   template <typename T>
   void
-  xinit (const octave_int<T>& base, const octave_int<T>& limit,
-         const octave_int<T>& inc, octave_int<T>& final_val,
-         octave_idx_type& nel)
+  xinit (const octave_int<T> base, const octave_int<T> limit,
+         const octave_int<T> inc, const bool reverse,
+         octave_int<T>& final_val, octave_idx_type& nel)
   {
     // We need an integer division that is truncating decimals instead
     // of rounding.  So, use underlying C++ types instead of
@@ -264,14 +269,28 @@
 
     // FIXME: The numerator might underflow or overflow. Add checks for
     // that.
+    if (reverse)
+      {
+        nel = ((inc == octave_int<T> (0)
+                || (limit > base && inc > octave_int<T> (0))
+                || (limit < base && inc < octave_int<T> (0)))
+               ? 0
+               : (base.value () - limit.value () + inc.value ())
+                 / inc.value ());
 
-    nel = ((inc == octave_int<T> (0)
-            || (limit > base && inc < octave_int<T> (0))
-            || (limit < base && inc > octave_int<T> (0)))
-           ? 0
-           : (limit.value () - base.value () + inc.value ()) / inc.value ());
+        final_val = base - (nel - 1) * inc;
+      }
+    else
+      {
+        nel = ((inc == octave_int<T> (0)
+                || (limit > base && inc < octave_int<T> (0))
+                || (limit < base && inc > octave_int<T> (0)))
+               ? 0
+               : (limit.value () - base.value () + inc.value ())
+                 / inc.value ());
 
-    final_val = base + (nel - 1) * inc;
+        final_val = base + (nel - 1) * inc;
+      }
   }
 
   template <typename T>
@@ -299,70 +318,70 @@
   void
   range<double>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<float>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_int8>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_int16>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_int32>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_int64>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_uint8>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_uint16>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_uint32>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
   void
   range<octave_uint64>::init (void)
   {
-    xinit (m_base, m_limit, m_increment, m_final, m_numel);
+    xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel);
   }
 
   template <>
diff -r 597275db9c7f -r f03166ed5fe1 liboctave/array/Range.h
--- a/liboctave/array/Range.h	Tue Nov 30 11:04:27 2021 -0500
+++ b/liboctave/array/Range.h	Tue Nov 30 18:19:20 2021 +0100
@@ -47,7 +47,8 @@
   public:
 
     range (void)
-      : m_base (0), m_increment (0), m_limit (0), m_final (0), m_numel (0)
+      : m_base (0), m_increment (0), m_limit (0), m_final (0), m_numel (0),
+        m_reverse (false)
     { }
 
     // LIMIT is an upper limit and may be outside the range of actual
@@ -55,15 +56,17 @@
     // to attempt to capture limit in the set of values if it is "close"
     // to the value of base + a multiple of the increment.
 
-    range (const T& base, const T& increment, const T& limit)
+    range (const T& base, const T& increment, const T& limit,
+           const bool& reverse = false)
       : m_base (base), m_increment (increment), m_limit (limit),
-        m_final (), m_numel ()
+        m_final (), m_numel (), m_reverse (reverse)
     {
       init ();
     }
 
     range (const T& base, const T& limit)
-      : m_base (base), m_increment (1), m_limit (limit), m_final (), m_numel ()
+      : m_base (base), m_increment (1), m_limit (limit), m_final (), m_numel (),
+        m_reverse (false)
     {
       init ();
     }
@@ -79,44 +82,47 @@
     // FIXME: Is there a way to limit this to T == double?
 
     range (const T& base, const T& increment, const T& limit,
-           octave_idx_type numel)
+           octave_idx_type numel, const bool& reverse = false)
       : m_base (base), m_increment (increment), m_limit (limit),
-        m_final (limit), m_numel (numel)
+        m_final (limit), m_numel (numel), m_reverse (reverse)
     { }
 
     range (const T& base, const T& increment, const T& limit,
-           const T& final, octave_idx_type numel)
+           const T& final, octave_idx_type numel, const bool& reverse = false)
       : m_base (base), m_increment (increment), m_limit (limit),
-        m_final (final), m_numel (numel)
+        m_final (final), m_numel (numel), m_reverse (reverse)
     { }
 
     // We don't use a constructor for this because it will conflict with
     // range<T> (base, limit) when T is octave_idx_type.
 
-    static range<T> make_constant (const T& base, octave_idx_type numel)
+    static range<T> make_constant (const T& base, octave_idx_type numel,
+                                   const bool& reverse = false)
     {
       // We could just make this constructor public, but it allows
       // inconsistent ranges to be constructed.  And it is probably much
       // clearer to see "make_constant" instead of puzzling over the
       // purpose of this strange constructor form.
 
-      return range<T> (base, T (), base, numel);
+      return range<T> (base, T (), base, numel, reverse);
     }
 
     // We don't use a constructor for this because it will conflict with
     // range<T> (base, limit, increment) when T is octave_idx_type.
 
     static range<T> make_n_element_range (const T& base, const T& increment,
-                                          octave_idx_type numel)
+                                          octave_idx_type numel,
+                                          bool reverse = false)
     {
       // We could just make this constructor public, but it allows
       // inconsistent ranges to be constructed.  And it is probably much
       // clearer to see "make_constant" instead of puzzling over the
       // purpose of this strange constructor form.
 
-      T final_val = base + (numel - 1) * increment;
+      T final_val = (reverse ? base - (numel - 1) * increment
+                             : base + (numel - 1) * increment);
 
-      return range<T> (base, increment, final_val, numel);
+      return range<T> (base, increment, final_val, numel, reverse);
     }
 
     range (const range<T>&) = default;
@@ -128,20 +134,23 @@
     T base (void) const { return m_base; }
     T increment (void) const { return m_increment; }
     T limit (void) const { return m_limit; }
+    bool reverse (void) const { return m_reverse; }
 
     T final_value (void) const { return m_final; }
 
     T min (void) const
     {
       return (m_numel > 0
-              ? m_increment > T (0) ? base () : final_value ()
+              ? ((m_reverse ? m_increment > T (0)
+                            : m_increment > T (0)) ? base () : final_value ())
               : T (0));
     }
 
     T max (void) const
     {
       return (m_numel > 0
-              ? m_increment > T (0) ? final_value () : base ()
+              ? ((m_reverse ? m_increment < T (0)
+                            : m_increment > T (0)) ? final_value () : base ())
               : T (0));
     }
 
@@ -170,12 +179,14 @@
 
     sortmode issorted (sortmode mode = ASCENDING) const
     {
-      if (m_numel > 1 && m_increment > T (0))
-        mode = (mode == DESCENDING) ? UNSORTED : ASCENDING;
-      else if (m_numel > 1 && m_increment < T (0))
-        mode = (mode == ASCENDING) ? UNSORTED : DESCENDING;
+      if (m_numel > 1 && (m_reverse ? m_increment < T (0)
+                                    : m_increment > T (0)))
+        mode = ((mode == DESCENDING) ? UNSORTED : ASCENDING);
+      else if (m_numel > 1 && (m_reverse ? m_increment > T (0)
+                                         : m_increment < T (0)))
+        mode = ((mode == ASCENDING) ? UNSORTED : DESCENDING);
       else
-        mode = (mode == UNSORTED) ? ASCENDING : mode;
+        mode = ((mode == UNSORTED) ? ASCENDING : mode);
 
       return mode;
     }
@@ -191,9 +202,10 @@
 
       if (i == 0)
         // Required for proper NaN handling.
-        return m_numel == 1 ? final_value () : m_base;
+        return (m_numel == 1 ? final_value () : m_base);
       else if (i < m_numel - 1)
-        return m_base + T (i) * m_increment;
+        return (m_reverse ? m_base + T (i) * m_increment
+                          : m_base + T (i) * m_increment);
       else
         return final_value ();
     }
@@ -211,9 +223,10 @@
     {
       if (i == 0)
         // Required for proper NaN handling.
-        return m_numel == 1 ? final_value () : m_base;
+        return (m_numel == 1 ? final_value () : m_base);
       else if (i < m_numel - 1)
-        return m_base + T (i) * m_increment;
+        return (m_reverse ? m_base - T (i) * m_increment
+                          : m_base + T (i) * m_increment);
       else
         return final_value ();
     }
@@ -266,9 +279,10 @@
           {
             if (i == 0)
               // Required for proper NaN handling.
-              *array++ = m_numel == 0 ? m_final : m_base;
+              *array++ = (m_numel == 0 ? m_final : m_base);
             else if (i < m_numel - 1)
-              *array++ = m_base + T (i) * m_increment;
+              *array++ = (m_reverse ? m_base - T (i) * m_increment
+                                    : m_base + T (i) * m_increment);
             else
               *array++ = m_final;
           });
@@ -297,8 +311,12 @@
           // E.g, -0 would otherwise become +0 in the loop (-0 + 0*increment).
           retval(0) = m_base;
 
-          for (octave_idx_type i = 1; i < nel - 1; i++)
-            retval.xelem (i) = m_base + i * m_increment;
+          if (m_reverse)
+            for (octave_idx_type i = 1; i < nel - 1; i++)
+              retval.xelem (i) = m_base - i * m_increment;
+          else
+            for (octave_idx_type i = 1; i < nel - 1; i++)
+              retval.xelem (i) = m_base + i * m_increment;
 
           retval.xelem (nel - 1) = final_value ();
         }
@@ -313,6 +331,7 @@
     T m_limit;
     T m_final;
     octave_idx_type m_numel;
+    bool m_reverse;
 
     // Setting the number of elements to zero when the increment is zero
     // is intentional and matches the behavior of Matlab's colon
@@ -323,13 +342,26 @@
 
     void init (void)
     {
-      m_numel = ((m_increment == T (0)
-                  || (m_limit > m_base && m_increment < T (0))
-                  || (m_limit < m_base && m_increment > T (0)))
-                 ? T (0)
-                 : (m_limit - m_base + m_increment) / m_increment);
+      if (m_reverse)
+        {
+          m_numel = ((m_increment == T (0)
+                      || (m_limit > m_base && m_increment > T (0))
+                      || (m_limit < m_base && m_increment < T (0)))
+                     ? T (0)
+                     : (m_base - m_limit - m_increment) / m_increment);
 
-      m_final = m_base + (m_numel - 1) * m_increment;
+          m_final = m_base - (m_numel - 1) * m_increment;
+        }
+      else
+        {
+          m_numel = ((m_increment == T (0)
+                      || (m_limit > m_base && m_increment < T (0))
+                      || (m_limit < m_base && m_increment > T (0)))
+                     ? T (0)
+                     : (m_limit - m_base + m_increment) / m_increment);
+
+          m_final = m_base + (m_numel - 1) * m_increment;
+        }
     }
   };
 
diff -r 597275db9c7f -r f03166ed5fe1 test/range.tst
--- a/test/range.tst	Tue Nov 30 11:04:27 2021 -0500
+++ b/test/range.tst	Tue Nov 30 18:19:20 2021 +0100
@@ -491,9 +491,10 @@
 %!error <colon operator lower bound invalid> (1.5:uint8(1):5)
 %!error <colon operator lower bound invalid> (-1:uint8(1):5)
 %!error <colon operator increment invalid> (uint8(1):1.5:5)
-%!error <colon operator increment invalid> (uint8(1):-1:5)
+%!error <colon operator increment invalid> (uint8(1):-256:5)
 %!error <colon operator upper bound invalid> (uint8(1):1:5.5)
 %!error <colon operator upper bound invalid> (uint8(1):1:256)
+%!error <colon operator upper bound invalid> (uint8(1):-1:-6)
 
 ## Extreme integer values.
 %!test <61132>
@@ -549,3 +550,15 @@
 %!   assert ((int_types{i_type} (95) : -6 : 0)([1,end]), ...
 %!           [int_types{i_type}(95), int_types{i_type}(5)]);
 %! endfor
+
+## Descending ranges, unsigned
+%!test <*61132>
+%! int_types = {@uint8, @uint16, @uint32, @uint64};
+%! for i_type = 1:numel (int_types)
+%!   for i_start = 0:4
+%!     assert ((int_types{i_type} (100-i_start) : -6 : 0)([1,end]), ...
+%!             [int_types{i_type}(100-i_start), int_types{i_type}(4-i_start)]);
+%!   endfor
+%!   assert ((int_types{i_type} (95) : -6 : 0)([1,end]), ...
+%!           [int_types{i_type}(95), int_types{i_type}(5)]);
+%! endfor
