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
- 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).
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
- 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
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
- Parameter must not be null, empty, or whitespace
- String.Format(Strings.MismatchedColumnValueType, this.DataTy
- ArgumentNullException(nameof(column))
- Strings.MismatchedColumnLengths
- offsetBuffer
AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11).
Data as JSON: /api/errors/e0e7a67c1fa06be5.
Report an issue: GitHub.