{"record":{"id":"e0e7a67c1fa06be5","repo":"dotnet/machinelearning","slug":"bad-comparer","errorCode":null,"errorMessage":"Bad comparer","messagePattern":"Bad comparer","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.Data.Analysis/DataFrameColumn.cs","lineNumber":625,"sourceCode":"            int middle = (int)(((uint)hi + (uint)lo) >> 1);\n\n            // Sort lo, mid and hi appropriately, then pick mid as the pivot.\n            Sort3(span, lo, middle, hi, sortIndices, comparer);\n\n            TKey pivot = span[sortIndices[middle]];\n\n            int left = lo;\n            int right = hi - 1;\n            // We already partitioned lo and hi and put the pivot in hi - 1.  \n            Swap(ref sortIndices[middle], ref sortIndices[right]);\n\n            while (left < right)\n            {\n                while (left < (hi - 1) && comparer.Compare(span[sortIndices[++left]], pivot) < 0)\n                    ;\n                // Check if bad comparable/comparer\n                if (left == (hi - 1) && comparer.Compare(span[sortIndices[left]], pivot) < 0)\n                    throw new ArgumentException(\"Bad comparer\");\n\n                while (right > lo && comparer.Compare(pivot, span[sortIndices[--right]]) < 0)\n                    ;\n                // Check if bad comparable/comparer\n                if (right == lo && comparer.Compare(pivot, span[sortIndices[right]]) < 0)\n                    throw new ArgumentException(\"Bad comparer\");\n\n                if (left >= right)\n                    break;\n\n                Swap(ref sortIndices[left], ref sortIndices[right]);\n            }\n            // Put pivot in the right location.\n            right = hi - 1;\n            if (left != right)\n            {\n                Swap(ref sortIndices[left], ref sortIndices[right]);\n            }","sourceCodeStart":607,"sourceCodeEnd":643,"githubUrl":"https://github.com/dotnet/machinelearning/blob/7b76e69cf964daeca3f1377af6bc5543284d56c6/src/Microsoft.Data.Analysis/DataFrameColumn.cs#L607-L643","documentation":"During quicksort partitioning inside PickPivotAndPartition, after the left scan advances, the code sanity-checks the comparer: if the left pointer reached the end of the range and the element there is still less than the pivot, the comparer is inconsistent (it violates the contract that Compare must be transitive and consistent with the pivot), so it throws ArgumentException(\"Bad comparer\"). This protects against infinite loops or corrupt sort output from a broken custom comparer.","triggerScenarios":"Sorting a column with a custom IComparer<T>/Comparison<T> delegate that is not a valid total order — e.g. returns contradictory results for the same pair, is non-transitive, or mishandles null/NaN values (NaN comparisons returning inconsistent signs).","commonSituations":"Hand-written comparators comparing doubles containing NaN; comparators with mutable external state; comparators returning randomized or non-deterministic results; float/NaN handling bugs in sort keys.","solutions":["Fix the custom comparer so it defines a consistent, transitive total order and handles NaN/null deterministically (e.g. treat NaN as less than everything, consistently).","Use built-in Comparer<T>.Default or Comparer<T>.Create with a simple, symmetric expression.","Pre-process data to remove/replace NaN or null keys before sorting.","Test the comparer against its own output: Compare(a,b) must equal -Compare(b,a)."],"exampleFix":"// before\nArray sort: (a, b) => double.IsNaN(a) ? 1 : a.CompareTo(b); // inconsistent with NaN on the other side\n// after\nComparer<double>.Create((a, b) =>\n{\n    if (double.IsNaN(a) && double.IsNaN(b)) return 0;\n    if (double.IsNaN(a)) return -1;\n    if (double.IsNaN(b)) return 1;\n    return a.CompareTo(b);\n});","handlingStrategy":"validation","validationCode":"// Verify comparer consistency on sample data before sorting\nfor (int i = 0; i < sample.Count - 1; i++)\n{\n    int ab = comparer.Compare(sample[i], sample[i + 1]);\n    int ba = comparer.Compare(sample[i + 1], sample[i]);\n    if (ab != -ba && !(ab == 0 && ba == 0))\n        throw new InvalidOperationException(\"Comparer is not a consistent total order.\");\n}","typeGuard":null,"tryCatchPattern":"try { column.Sort(comparer); }\ncatch (ArgumentException ex) when (ex.Message == \"Bad comparer\")\n{\n    // fall back to a default comparer or reject the custom comparer\n    column.Sort(Comparer<T>.Default);\n}","preventionTips":["Never write stateful or randomized comparers","Handle NaN/null explicitly and symmetrically in comparators","Unit-test comparers for antisymmetry and transitivity","Prefer Comparer<T>.Default or Comparer<T>.Create with simple expressions"],"tags":["dotnet","sorting","comparer","argument-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"7b76e69cf964daeca3f1377af6bc5543284d56c6","analyzedAt":"2026-09-11T12:35:38.930Z","contentChangedAt":"2026-09-11T12:35:38.930Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}