changeset 24308:606f3866cdb7

__isequal__.m: Rewrite function for performance. * isequal.m: Add many more BIST tests. Most are to verify the same thing through the two different internal code paths (2 args vs. multiple args) in __isequal__.m * __isequal__.m: Add Rik Wehbring to Copyright. Rewrite docstring and comments describing algorithm. Add special fast path for only two arguments (most common case). Use size_equal() rather than hand-rolled code to check ndims() and size() along each dimension. Use strcmp without reshaping for better performance. Add special fast conversion for cellstr type by converting to char arrays and using strcmp. Put if/elseif branches in rough order of probability so common cases are hit first. Only run isnan tests on floating point objects which can actually have NaN values. Rename variables for clarity.
author Rik <rik@octave.org>
date Sat, 25 Nov 2017 21:56:15 -0800
parents ddaee520d342
children 1262d7c4712e
files scripts/general/isequal.m scripts/general/private/__isequal__.m
diffstat 2 files changed, 308 insertions(+), 161 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/general/isequal.m	Fri Nov 24 17:29:00 2017 -0800
+++ b/scripts/general/isequal.m	Sat Nov 25 21:56:15 2017 -0800
@@ -35,10 +35,10 @@
 
 ## test empty input
 %!assert (isequal ([], []), true)
+%!assert (isequal ([], 1), false)
 %!assert (isequal ([], [], 1), false)
 %!assert (isequal ([], 1, []), false)
 %!assert (isequal (1, [], []), false)
-%!assert (isequal (1, [], []), false)
 
 ## test size and shape
 %!assert (isequal ([1,2,3,4], [1,2,3,4]), true)
@@ -46,87 +46,145 @@
 %!assert (isequal ([1,2,3,4], [1;2;3;4]), false)
 %!assert (isequal ([1,2,3,4], [1,2;3,4]), false)
 %!assert (isequal ([1,2,3,4], [1,3;2,4]), false)
+%!assert (isequal ([1,2,3,4], [1,2,3,4], [1,2,3,4]), true)
+%!assert (isequal ([1;2;3;4], [1;2;3;4], [1;2;3;4]), true)
+%!assert (isequal ([1,2,3,4], [1,2,3,4], [1;2;3;4]), false)
+%!assert (isequal ([1,2,3,4], [1,2,3,4], [1,2;3,4]), false)
+%!assert (isequal ([1,2,3,4], [1,2,3,4], [1,3;2,4]), false)
 
+## General tests
 %!test
 %! A = 1:8;
 %! B = reshape (A, 2, 2, 2);
 %! assert (isequal (A, B), false);
+%! assert (isequal (A, A, B), false);
 %!test
 %! A = reshape (1:8, 2, 2, 2);
 %! B = A;
 %! assert (isequal (A, B), true);
+%! assert (isequal (A, A, B), true);
 %!test
 %! A = reshape (1:8, 2, 4);
 %! B = reshape (A, 2, 2, 2);
 %! assert (isequal (A, B), false);
+%! assert (isequal (A, A, B), false);
+
+## test characters and strings
+%!assert (isequal ('a', "a"), true)
+%!assert (isequal ('a', 'a', "a"), true)
+%!assert (isequal ("abab", ["a", "b", "a", "b"]), true)
+%!assert (isequal ("abab", "abab", ["a", "b", "a", "b"]), true)
+%!assert (isequal (["a","b","c","d"], ["a","b","c","d"]), true)
+%!assert (isequal (["a","b","c","d"], ["a","b","c","d"], ["a","b","c","d"]),
+%!        true)
+%!assert (isequal (["test   ";"strings"], ["test   ";"strings"]), true)
+%!assert (isequal (["test   ";"strings"], ["test   ";"strings"],
+%!                 ["test   ";"strings"]), true)
+%!assert (isequal (["a","b","c","d"], ["a";"b";"c";"d"]), false)
+%!assert (isequal (["a","b","c","d"], ["a","b","c","d"], ["a";"b";"c";"d"]),
+%!        false)
 
 ## test all numeric built-in primitives
+%!assert (isequal (false, 0))
+%!assert (isequal (char (0), 0))
 %!assert (isequal (false, logical (0), char (0),
 %!                 int8 (0), int16 (0), int32 (0), int64 (0),
 %!                 uint8 (0), uint16 (0), uint32 (0), uint64 (0),
 %!                 double (0), single (0),
-%!                 double (complex (0,0)), single (complex (0,0))),
+%!                 double (complex (0,0)), single (complex (0,0)),
+%!                 sparse (false), sparse (logical (0)),
+%!                 sparse (double (0)), sparse (single (0)),
+%!                 sparse (double (complex (0,0))),
+%!                 sparse (single (complex (0,0)))),
 %!        true)
 %!assert (isequal (true, logical (1), char (1),
 %!                 int8 (1), int16 (1), int32 (1), int64 (1),
 %!                 uint8 (1), uint16 (1), uint32 (1), uint64 (1),
 %!                 double (1), single (1),
-%!                 double (complex (1,0)), single (complex (1,0))),
+%!                 double (complex (1,0)), single (complex (1,0)),
+%!                 sparse (true), sparse (logical (1)),
+%!                 sparse (double (1)), sparse (single (1)),
+%!                 sparse (double (complex (1,0))),
+%!                 sparse (single (complex (1,0)))),
 %!        true)
 
-## test characters and strings
-%!assert (isequal ('a', "a"), true)
-%!assert (isequal ("abab", ["a", "b", "a", "b"]), true)
-%!assert (isequal (["a","b","c","d"], ["a","b","c","d"]), true)
-%!assert (isequal (["test   ";"strings"], ["test   ";"strings"],
-%!                 ["test   ";"strings"]), true)
-%!assert (isequal (["a","b","c","d"], ["a";"b";"c";"d"]), false)
-
-## test function_handle
-%!test
-%! fcn = @(x) x.^2;
-%! assert (isequal (fcn, fcn), true);
-%! assert (isequal (fcn, @(x) x.^2), false);
-%! assert (isequal (@(x) x.^2, fcn), false);
-
 ## test structures
-%!assert (isequal (struct ([]),struct ([])), true)
+%!assert (isequal (struct ([]), struct ([])), true)
+%!assert (isequal (struct ([]), struct ([]), struct ([])), true)
 %!assert (isequal (struct ("a",1), struct ("a",1)), true)
+%!assert (isequal (struct ("a",1), struct ("a",1), struct ("a",1)), true)
 %!assert (isequal (struct ("a",1), struct ("a",2)), false)
+%!assert (isequal (struct ("a",1), struct ("a",1), struct ("a",2)), false)
+%!assert (isequal (struct ("a",1), struct ("a",1,"b",2)), false)
+%!assert (isequal (struct ("a",1), struct ("a",1),struct ("a",1,"b",2)), false)
 %!assert (isequal (struct ("a",1), struct ("b",1)), false)
+%!assert (isequal (struct ("a",1), struct ("a",1), struct ("b",1)), false)
 %!assert (isequal (struct ("a",1,"b",2), struct ("a",1,"b",2)), true)
+%!assert (isequal (struct ("a",1,"b",2), struct ("a",1,"b",2),
+%!                 struct ("a",1,"b",2)), true)
 %!assert (isequal (struct ("a",1,"b",2), struct ("b",2,"a",1)), true)
 %!assert (isequal (struct ("a",1,"b",2), struct ("a",1,"b",2),
-%!                 struct ("a",1,"b",2)), true)
+%!                 struct ("b",2,"a",1)), true)
 %!assert (isequal (struct ("a","abc","b",2), struct ("a","abc","b",2)), true)
+%!assert (isequal (struct ("a","abc","b",2), struct ("a","abc","b",2), 
+%!                 struct ("a","abc","b",2)), true)
 
 ## recursive structure
 %!test
 %! x.a = "a1";
 %! x.b.a = "ba1";
 %! x.b.b = "bb1";
+%! assert (isequal (x, x), true);
 %! assert (isequal (x, x, x), true);
 %! y = x;
 %! y.b.b = "bb2";
 %! assert (isequal (x, y), false);
+%! assert (isequal (x, x, y), false);
 %! y = x;
 %! y.b = rmfield (y.b, "b");
 %! y.b.b.a = "bba1";
 %! assert (isequal (x, y), false);
+%! assert (isequal (x, x, y), false);
+
+## test cellstr
+%!assert (isequal (cell (1,1), cell (1,1)), true)
+%!assert (isequal (cell (1,1), cell (1,2)), false)
+%!assert (isequal ({"a","b";"c","d"}, {"a","b";"c","d"}), true)
+%!assert (isequal ({"a","b";"c","d"}, {"a","b";"c","d"}, {"a","b";"c","d"}),
+%!                 true)
+%!assert (isequal ({"a","b","c","d"}, {"a";"b";"c";"d"}), false)
+%!assert (isequal ({"a","b","c","d"}, {"a","b","c","d"}, {"a";"b";"c";"d"}),
+%!        false)
+%!assert (isequal (["a","b","c","d"], {"a","b","c","d"}), false)
+%!assert (isequal (["a","b","c","d"], ["a","b","c","d"], {"a","b","c","d"}),
+%!        false)
+%!test
+%! x = { ["ab"; "cd"] ; ["ef"; "gh"] };
+%! assert (isequal (x, x), true);
+%! assert (isequal (x, x, x), true);
+%! y = x;
+%! y(2) = ["ef"; "gH"];
+%! assert (isequal (x, y), false);
+%! assert (isequal (x, x, y), false);
 
 ## test cells
 %!assert (isequal (cell (1,1), cell (1,1)), true)
+%!assert (isequal (cell (1,1), cell (1,1), cell (1,1)), true)
 %!assert (isequal (cell (1,1), cell (1,2)), false)
+%!assert (isequal (cell (1,1), cell (1,1), cell (1,2)), false)
 %!assert (isequal ({"a",1}, {"a",1}), true)
+%!assert (isequal ({"a",1}, {"a",1}, {"a",1}), true)
 %!assert (isequal ({"a",1}, {"a",2}), false)
+%!assert (isequal ({"a",1}, {"a",1}, {"a",2}), false)
 %!assert (isequal ({"a",1}, {"b",1}), false)
+%!assert (isequal ({"a",1}, {"a",1}, {"b",1}), false)
 %!assert (isequal ({"a",1,"b",2}, {"a",1,"b",2}), true)
+%!assert (isequal ({"a",1,"b",2}, {"a",1,"b",2}, {"a",1,"b",2}), true)
 %!assert (isequal ({"a",1,"b",2}, {"b",2,"a",1}), false)
-%!assert (isequal ({"a",1,"b",2}, {"a",1,"b",2}, {"a",1,"b",2}), true)
+%!assert (isequal ({"a",1,"b",2}, {"a",1,"b",2}, {"b",2,"a",1}), false)
 %!assert (isequal ({"a","abc","b",2}, {"a","abc","b",2}), true)
-%!assert (isequal ({"a","b","c","d"}, {"a","b","c","d"}), true)
-%!assert (isequal ({"a","b","c","d"}, {"a";"b";"c";"d"}), false)
-%!assert (isequal (["a","b","c","d"], {"a","b","c","d"}), false)
+%!assert (isequal ({"a","abc","b",2}, {"a","abc","b",2}, {"a","abc","b",2}),
+%!                 true)
 
 ## recursive cell
 %!test
@@ -139,26 +197,49 @@
 %! y{3}{1}{1} = "goodbye";
 %! assert (isequal (x, y), false);
 
+## test function_handle
+%!test
+%! fcn = @(x) x.^2;
+%! assert (isequal (fcn, fcn), true);
+%! assert (isequal (fcn, fcn, fcn), true);
+%! assert (isequal (fcn, @(x) x.^2), false);
+%! assert (isequal (fcn, fcn, @(x) x.^2), false);
+%! assert (isequal (@(x) x.^2, fcn), false);
+%! assert (isequal (@(x) x.^2, @(x) x.^2, fcn), false);
+
 ## test for sparse matrices
 %!assert (isequal (sparse ([]), []), true)
+%!assert (isequal (sparse ([]), sparse ([]), []), true)
 %!assert (isequal ([], sparse ([])), true)
+%!assert (isequal ([], [], sparse ([])), true)
 %!assert (isequal (sparse (0,1), sparse (0,1)), true)
+%!assert (isequal (sparse (0,1), sparse (0,1), sparse (0,1)), true)
 %!assert (isequal (sparse (0,1), zeros (0,1)), true)
+%!assert (isequal (sparse (0,1), sparse (0,1), zeros (0,1)), true)
 %!assert (isequal (sparse (2,2), sparse (2,2)), true)
+%!assert (isequal (sparse (2,2), sparse (2,2), sparse (2,2)), true)
 %!assert (isequal (zeros (2,2), sparse (2,2)), true)
+%!assert (isequal (zeros (2,2), zeros (2,2), sparse (2,2)), true)
 %!assert (isequal (speye (1), eye (1)), true)
+%!assert (isequal (speye (1), speye (1), eye (1)), true)
 %!assert (isequal (eye (300), speye (300)), true)
+%!assert (isequal (eye (300), eye (300), speye (300)), true)
 %!assert (isequal (sparse (0,1), sparse (1,0)), false)
+%!assert (isequal (sparse (0,1), sparse (0,1), sparse (1,0)), false)
 
 ## test NaN
 %!assert (isequal (NaN, NaN), false)
+%!assert (isequal (NaN, NaN, NaN), false)
 %!assert (isequal (NaN, Inf), false)
+%!assert (isequal (NaN, Inf, Inf), false)
 %!assert (isequal (NaN, 1.0), false)
+%!assert (isequal (NaN, 1.0, 1.0), false)
 %!assert (isequal ([1,2,NaN,4], [1,2,NaN,4]), false)
+%!assert (isequal ([1,2,NaN,4], [1,2,NaN,4], [1,2,NaN,4]), false)
+%!assert (isequal (struct ("a",NaN,"b",2), struct ("a",NaN,"b",2)), false)
 %!assert (isequal (struct ("a",NaN,"b",2), struct ("a",NaN,"b",2),
 %!                 struct ("a",NaN,"b",2)), false)
 
 ## test input validation
 %!error isequal ()
 %!error isequal (1)
-%!error isequal ([1,1])
--- a/scripts/general/private/__isequal__.m	Fri Nov 24 17:29:00 2017 -0800
+++ b/scripts/general/private/__isequal__.m	Sat Nov 25 21:56:15 2017 -0800
@@ -1,3 +1,4 @@
+## Copyright (C) 2017 Rik Wehbring
 ## Copyright (C) 2000-2017 Paul Kienzle
 ##
 ## This file is part of Octave.
@@ -16,173 +17,238 @@
 ## along with Octave; see the file COPYING.  If not, see
 ## <http://www.gnu.org/licenses/>.
 
-## Undocumented internal function.
-
 ## -*- texinfo -*-
-## @deftypefn {} {} __isequal__ (@var{nans_compare_equal}, @var{x1}, @var{x2}, @dots{})
-## Undocumented internal function.
-## @end deftypefn
-
-## Return true if @var{x1}, @var{x2}, @dots{} are all equal and
+## @deftypefn {} {} __isequal__ (@var{nans_compare_equal}, @var{x}, @var{y}, @dots{})
+## Internal function.
+##
+## Return true if @var{x}, @var{y}, @dots{} are all equal and
 ## @var{nans_compare_equal} evaluates to false.
 ##
 ## If @var{nans_compare_equal} evaluates to true, then assume NaN == NaN.
-
-## Modified by: William Poetra Yoga Hadisoeseno
+## @end deftypefn
 
 ## Algorithm:
 ##
-## 1. Determine the class of x
-## 2. If x and all other arguments have the same class, then check that the
-##    number of dimensions and then the size of each dimension match.
-##    If not all arguments share the same class, then verify that all of the
-##    arguments belong to a comparable "numeric" class which includes
-##    numeric, logical, and character arrays.  Check that number of dimensions
-##    and size of each dimension match.
-## 3. For each argument after x, compare it for equality with x:
-##    a. struct     compare each member by name, not by order (recursive)
-##    b. object     convert to struct, and then compare as stated above
-##    c. cell       compare each member by order (recursive)
-##    d. char       compare each member with strcmp
-##    e  fcn_handle compare using overloaded 'eq' operator
-##    f. <other>    compare each nonzero member, and assume NaN == NaN
+## 1. Verify the class of x.
+##    a. All objects are of the same class
+##    b. All objects are of a generic "numeric" class which includes
+##       numeric, logical, and character arrays
+## 2. Verify size of all objects match.
+## 3. Convert objects to struct, and then compare as stated below.
+## 4. For each argument after x, compare it for equality with x:
+##    a. char       compare each member with strcmp
+##    b. numeric    compare each member with '==', and assume NaN == NaN
 ##                  if nans_compare_equal is nonzero.
+##    c. struct     compare number of fieldnames, value of fieldnames,
+##                  and then each field with __isequal__ (recursive)
+##    d. cellstr    compare each cellstr member with strcmp
+##    e. cell       compare each member with __isequal__ (recursive)
+##    f. fcn_handle compare using overloaded "eq" operator
 
 function t = __isequal__ (nans_compare_equal, x, varargin)
 
-  l_v = nargin - 2;
-
-  ## Generic tests.
+  nvarargin = nargin - 2;
+  two_args = (nvarargin == 1);  # Optimization for base case of just 2 args
 
-  ## All arguments must either be of the same class or they must be
-  ## "numeric" values.
-  t = (all (strcmp (class (x),
-                    cellfun ("class", varargin, "uniformoutput", false)))
-       || ((isreal (x) || isnumeric (x))
-           && all (cellfun ("isreal", varargin)
-                   | cellfun ("isnumeric", varargin))));
-
-  if (t)
-    ## Test that everything has the same number of dimensions.
-    t = all (builtin ("ndims", x) == cellfun ("ndims", varargin));
+  if (two_args)
+    y = varargin{1};  # alias y to second input for comparison
   endif
 
-  if (t)
-    ## Test that everything is the same size since the dimensionality matches.
-    nd = ndims (x);
-    k = 1;
-    do
-      t = all (builtin ("size", x, k) == cellfun ("size", varargin, k));
-    until (! t || k++ == nd);
+  ############################################################
+  ## Generic tests for equality
+
+  ## All arguments must either be of the same class,
+  ##  or they must be "numeric" values.
+  if (two_args)
+    t = (strcmp (class (x), class (y))
+         || ((isreal (x) || iscomplex (x)) && (isreal (y) || iscomplex (y))));
+  else
+    t = (all (cellfun ("isclass", varargin, class (x)))
+         || ((isreal (x) || iscomplex (x))
+             && all (cellfun ("isreal", varargin)
+                     | cellfun ("isnumeric", varargin))));
   endif
 
-  ## From here on, compare objects as if they were structures.
+  ## Test that everything is the same size (which also tests dimensions)
+  if (t)
+    t = size_equal (x, varargin{:});
+  endif
+
+  ## From here on, compare any objects as if they were structures.
   if (t && isobject (x))
     ## Locally suppress class-to-struct warning.  We know what we are doing.
     warning ("off", "Octave:classdef-to-struct", "local");
     x = builtin ("struct", x);
-    for i = 1:numel (varargin)
-      varargin{i} = builtin ("struct", varargin{i});
+    for i = 1:nvarargin
+      varargin(i) = builtin ("struct", varargin{i});
     endfor
   endif
 
-  if (t)
-    ## Check individual classes.
-    if (isstruct (x))
-      ## Test the number of fields.
-      fn_x = fieldnames (x);
-      l_fn_x = numfields (x);
-      fn_v = cellfun ("fieldnames", varargin, "uniformoutput", false);
-      t = all (l_fn_x == cellfun ("numel", fn_v));
-
-      ## Test that all the names are equal.
-      idx = 0;
-      s_fn_x = sort (fn_x);
-      while (t && idx < l_v)
-        idx += 1;
-        ## We'll allow the fieldnames to be in a different order.
-        t = all (strcmp (s_fn_x, sort (fn_v{idx})));
-      endwhile
+  ############################################################
+  ## Check individual classes.
 
-      idx = 0;
-      while (t && idx < l_fn_x)
-        ## Test that all field values are equal.
-        idx += 1;
-        args = cell (1, 2+l_v);
-        args(1:2) = {nans_compare_equal, {x.(fn_x{idx})}};
-        for argn = 1:l_v
-          args{argn+2} = {varargin{argn}.(fn_x{idx})};
-        endfor
-        ## Minimize function calls by calling for all the arguments at once.
-        t = __isequal__ (args{:});
-      endwhile
-
-    elseif (iscellstr (x) && all (cellfun (@iscellstr, varargin)))
-      ## Check that each element of a cellstr is equal.
-      l_x = numel (x);
-      idx = 0;
-      ## FIXME: It would be faster to use strcmp on whole cellstr arrays,
-      ## rather than element-by-element, but bug #51412 needs to be fixed.
-      while (t && idx < l_x)
-        idx += 1;
-        t = all (strcmp (x{idx}, [cellindexmat(varargin, idx){:}]));
-      endwhile
+  if (t)
+    if (two_args)
 
-    elseif (iscell (x))
-      ## Check that each element of a cell is equal.
-      l_x = numel (x);
-      idx = 0;
-      args = cell (1, 2+l_v);
-      args{1} = nans_compare_equal;
-      while (t && idx < l_x)
-        idx += 1;
-        args{2} = x{idx};
-        args(3:end) = [cellindexmat(varargin, idx){:}];
-
-        t = __isequal__ (args{:});
-      endwhile
-
-    elseif (ischar (x) && all (cellfun ("isclass", varargin, "char")))
-      ## Sizes are equal already, so we can just make everything into a
-      ## row and test the rows.
-      n_x = numel (x);
-      strings = cell (1, l_v);
-      for i = 1:l_v
-        strings{i} = reshape (varargin{i}, 1, n_x);
-      endfor
-      t = all (strcmp (reshape (x, 1, n_x), strings));
+      if (ischar (x) && ischar (y))
+        ## char type.  Optimization, strcmp is ~35% faster than '==' operator.
+        t = strcmp (x, y);
 
-    elseif (isa (x, "function_handle"))
-      ## The == operator is overloaded for handles.
-      t = all (cellfun ("eq", {x}, varargin));
-
-    else
-      ## Check the numeric types.
+      elseif (isreal (x) || iscomplex (x))
+        ## general "numeric" type.  Use '==' operator.
+        m = (x == y);
+        t = all (m(:));
 
-      f_x = find (x);
-      l_f_x = length (f_x);
-      x = x(f_x);
-      for argn = 1:l_v
-        y = varargin{argn};
-        f_y = find (y);
-
-        t = (l_f_x == length (f_y)) && all (f_x == f_y);
-        if (! t)
-          break;
-        endif
-
-        y = y(f_y);
-        m = (x == y);
-        t = all (m);
-
-        if (! t && nans_compare_equal)
+        if (! t && nans_compare_equal && isfloat (x) && isfloat (y))
           t = isnan (x(! m)) && isnan (y(! m));
         endif
 
-        if (! t)
-          break;
+      elseif (isstruct (x))
+        ## struct type.  Compare # of fields, fieldnames, then field values.
+
+        ## Test number of fields are equal.
+        t = (numfields (x) == numfields (y));
+
+        ## Test that all the field names are equal.
+        if (t)
+          s_fnm_x = sort (fieldnames (x));
+          t = all (strcmp (s_fnm_x, sort (fieldnames (y))));
+        endif
+
+        ## Test that all field values are equal.  Slow because of recursion.
+        if (t)
+          for fldnm = s_fnm_x.'
+            t = __isequal__ (nans_compare_equal, x.(fldnm{1}), y.(fldnm{1}));
+            if (! t)
+              break;
+            endif
+          endfor
         endif
-      endfor
+
+      elseif (iscellstr (x) && iscellstr (y))
+        ## cellstr type.  Optimization over cell type by using strcmp.
+        ## FIXME: It would be faster to use strcmp on whole cellstr arrays,
+        ## but bug #51412 needs to be fixed.  Instead, time/space trade-off.
+        ## Convert to char (space) for faster processing with strcmp (time).
+        t = strcmp (char (x), char (y));
+
+      elseif (iscell (x))
+        ## cell type.  Check that each element of a cell is equal.  Slow.
+        n = numel (x);
+        idx = 1;
+        while (t && idx <= n)
+          t = __isequal__ (nans_compare_equal, x{idx}, y{idx});
+          idx += 1;
+        endwhile
+
+      elseif (isa (x, "function_handle"))
+        ## function type.  Use '==' operator which is overloaded.
+        t = (x == y);
+
+      else
+        error ("__isequal__: Impossible to reach code.  File a bug report."); 
+
+      endif
+
+    else  ## More than two args.  This is going to be slower in general.
+
+      if (ischar (x) && all (cellfun ("isclass", varargin, "char")))
+        ## char type.  Optimization, strcmp is ~35% faster than '==' operator.
+        idx = 1;
+        while (t && idx <= nvarargin)
+          t = strcmp (x, varargin{idx});
+          idx += 1;
+        endwhile
+
+      elseif (isreal (x) || iscomplex (x))
+        ## general "numeric" type.  Use '==' operator.
+
+        idx = 1;
+        while (t && idx <= nvarargin)
+          y = varargin{idx};
+          m = (x == y);
+          t = all (m(:));
+
+          if (! t && nans_compare_equal && isfloat (x) && isfloat (y))
+            t = isnan (x(! m)) && isnan (y(! m));
+          endif
+
+          idx += 1;
+        endwhile
+
+      elseif (isstruct (x))
+        ## struct type.  Compare # of fields, fieldnames, then field values.
+
+        ## Test number of fields are equal.
+        fnm_x = fieldnames (x);
+        n = numel (fnm_x);
+        fnm_v = cellfun ("fieldnames", varargin, "uniformoutput", false);
+        t = all (n == cellfun ("numel", fnm_v));
+
+        ## Test that all the field names are equal.
+        if (t)
+          fnm_x = sort (fnm_x);
+          idx = 1;
+          while (t && idx <= nvarargin)
+            ## Allow the fieldnames to be in a different order.
+            t = all (strcmp (fnm_x, sort (fnm_v{idx})));
+            idx += 1;
+          endwhile
+        endif
+
+        ## Test that all field values are equal.  Slow because of recursion.
+        if (t)
+          args = cell (1, 2 + nvarargin);
+          args(1) = nans_compare_equal;
+          for fldnm = fnm_x.'
+            args(2) = x.(fldnm{1});
+            for argn = 1:nvarargin
+              args(argn+2) = varargin{argn}.(fldnm{1});
+            endfor
+
+            t = __isequal__ (args{:});
+
+            if (! t)
+              break;
+            endif
+          endfor
+        endif
+
+      elseif (iscellstr (x) && all (cellfun (@iscellstr, varargin)))
+        ## cellstr type.  Optimization over cell type by using strcmp.
+        ## FIXME: It would be faster to use strcmp on whole cellstr arrays,
+        ## but bug #51412 needs to be fixed.  Instead, time/space trade-off.
+        ## Convert to char (space) for faster processing with strcmp (time).
+        idx = 1;
+        x = char (x);
+        while (t && idx <= nvarargin) 
+          t = strcmp (x, char (varargin{idx}));
+          idx += 1;
+        endwhile
+
+      elseif (iscell (x))
+        ## cell type.  Check that each element of a cell is equal.  Slow.
+        n = numel (x);
+        args = cell (1, 2 + nvarargin);
+        args(1) = nans_compare_equal;
+        idx = 1;
+        while (t && idx <= n)
+          args(2) = x{idx};
+          args(3:end) = [cellindexmat(varargin, idx){:}];
+
+          t = __isequal__ (args{:});
+
+          idx += 1;
+        endwhile
+
+      elseif (isa (x, "function_handle"))
+        ## function type.  Use '==' operator which is overloaded.
+        t = all (cellfun ("eq", {x}, varargin));
+
+      else
+        error ("__isequal__: Impossible to reach code.  File a bug report."); 
+
+      endif
 
     endif
   endif