dotnet/machinelearning · error · ArgumentException

Bad comparer

Error message

Bad comparer

What it means

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.

Source

Thrown at src/Microsoft.Data.Analysis/DataFrameColumn.cs:625

            int middle = (int)(((uint)hi + (uint)lo) >> 1);

            // Sort lo, mid and hi appropriately, then pick mid as the pivot.
            Sort3(span, lo, middle, hi, sortIndices, comparer);

            TKey pivot = span[sortIndices[middle]];

            int left = lo;
            int right = hi - 1;
            // We already partitioned lo and hi and put the pivot in hi - 1.  
            Swap(ref sortIndices[middle], ref sortIndices[right]);

            while (left < right)
            {
                while (left < (hi - 1) && comparer.Compare(span[sortIndices[++left]], pivot) < 0)
                    ;
                // Check if bad comparable/comparer
                if (left == (hi - 1) && comparer.Compare(span[sortIndices[left]], pivot) < 0)
                    throw new ArgumentException("Bad comparer");

                while (right > lo && comparer.Compare(pivot, span[sortIndices[--right]]) < 0)
                    ;
                // Check if bad comparable/comparer
                if (right == lo && comparer.Compare(pivot, span[sortIndices[right]]) < 0)
                    throw new ArgumentException("Bad comparer");

                if (left >= right)
                    break;

                Swap(ref sortIndices[left], ref sortIndices[right]);
            }
            // Put pivot in the right location.
            right = hi - 1;
            if (left != right)
            {
                Swap(ref sortIndices[left], ref sortIndices[right]);
            }

View on GitHub (pinned to 7b76e69cf9)

Solutions

  1. 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).
  2. Use built-in Comparer<T>.Default or Comparer<T>.Create with a simple, symmetric expression.
  3. Pre-process data to remove/replace NaN or null keys before sorting.
  4. Test the comparer against its own output: Compare(a,b) must equal -Compare(b,a).

Example fix

// before
Array sort: (a, b) => double.IsNaN(a) ? 1 : a.CompareTo(b); // inconsistent with NaN on the other side
// after
Comparer<double>.Create((a, b) =>
{
    if (double.IsNaN(a) && double.IsNaN(b)) return 0;
    if (double.IsNaN(a)) return -1;
    if (double.IsNaN(b)) return 1;
    return a.CompareTo(b);
});
Defensive patterns

Strategy: validation

Validate before calling

// Verify comparer consistency on sample data before sorting
for (int i = 0; i < sample.Count - 1; i++)
{
    int ab = comparer.Compare(sample[i], sample[i + 1]);
    int ba = comparer.Compare(sample[i + 1], sample[i]);
    if (ab != -ba && !(ab == 0 && ba == 0))
        throw new InvalidOperationException("Comparer is not a consistent total order.");
}

Try / catch

try { column.Sort(comparer); }
catch (ArgumentException ex) when (ex.Message == "Bad comparer")
{
    // fall back to a default comparer or reject the custom comparer
    column.Sort(Comparer<T>.Default);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11). Data as JSON: /api/errors/e0e7a67c1fa06be5. Report an issue: GitHub.