2

Given a pair of floating-point numbers, what's the best way to perform a three-way comparison, that is, return negative, zero or positive, depending on whether the first number is less than, equal to or greater than the second number?

Some languages like Perl, Java and C++20, have this operator built-in, or in the standard library. However, I'm asking how to do this in plain C.

If the inputs were integers, it would be a simple matter of writing a pair of two-way comparisons. But it's more complicated with floating-point inputs because of the special behavior of NaNs in comparison. What's the appropriate way to do it, taking that into account?

rwallace
  • 31,405
  • 40
  • 123
  • 242
  • You might also want to consider an "epsilon" for equality. Anyway, what for do you need this? I cannot remember any case when I needed such. – the busybee Dec 15 '21 at 12:56
  • 4
    How do you want the result provided? As an integer that is −1, 0, or +1 for less than, equal to, or greater than, or as an integer that is any negative number, zero, or any positive number for less than, equal, to, or greater than? Or something else? What result do you want if one or both of the operands is a NaN? – Eric Postpischil Dec 15 '21 at 12:56
  • 1
    You need to distinguish 4 different comparison results, less than, equal, greater than, and unordered. I would probably use the `isunordered(x, y)` macro to test for that first. You will need a 4-way value to represent it. – Ian Abbott Dec 15 '21 at 12:57
  • @BarmakShemirani 1) If it works on one system, it proves nothing. Other systems might give other results. 2) Did you also check for minimum, maximum, NaN and infinity values? 3) The OP did not tell us the data type of the required result. It might not be floating point. – the busybee Dec 15 '21 at 13:39
  • @thebusybee I wasn't presenting proof. I was expecting failure for small number comparison, I think I got confused about precision comparison. I didn't check the other side. – Barmak Shemirani Dec 15 '21 at 14:19
  • 3
    rwallace, Must return type be `int`? How about `double`? This allows returning `NAN` where `x` or `y` is `NAN`. When comparing +/- 0.0 to +/- 0.0 (4 combinations), I'd expect you want a return _value_ of 0.0. OK if it is -0.0 sometimes or must it be +0.0? – chux - Reinstate Monica Dec 15 '21 at 14:23

2 Answers2

3

Returns -1, 0, 1 for less, equal and greater, plus some other value for not comparable:

#include <math.h>

int cmp(double a, double b) {
    if (isunordered(a, b)) {
        /* at least one NaN, return whatever you feel is appropriate */
        return 42;
    }
    return (a>b) - (a<b);
}

If you want fuzzy comparisons with epsilon, feel free to make the < and > bit more complicated...

  • 1
    The `a!=a || b!=b` test could be replaced with `isunordered(a,b)`. – Ian Abbott Dec 15 '21 at 12:59
  • 2
    Are you sure of your anwer ? From this link: https://stackoverflow.com/questions/38798791/nan-comparison-rule-in-c-c/38799236 it looks more complicated – Guillaume Petitjean Dec 15 '21 at 13:05
  • 2
    "The == and != operators appear to not be constrained to the IEEE 754 behavior for NaNs, as pointed out in @AlexD's answer already. However, the comparison macros are required to follow NaN rules equivalent to IEEE 754's. " – Guillaume Petitjean Dec 15 '21 at 13:05
  • 1
    Note that this compare approach is not valid for use with `qsort()` as `cmp(NAN, not NAN)` is not `-cmp(not NAN, NAN)` (unless the NAN return value is 0). – chux - Reinstate Monica Dec 15 '21 at 15:09
3

What's the appropriate way to do it, taking that (NAN) into account?

Return a FP type to allow 4 different returns values: -1.0, 0.0, 1.0, NAN.

Below also returns -0.0 in select cases involving -0.0.

#include <math.h>

double fcmp(double a, double b) {
  if (isunordered(a, b)) return NAN;
  if (a > b) return 1.0; 
  if (a < b) return -1.0; 
  return a - b;
} 

I'd even consider propagating the a or b when one is NAN to maintain the NAN payload. There may exist many different non-a-numbers.

double fcmp(double a, double b) {
  if (isnan(a)) return a;
  if (isnan(b)) return b;
  ...
} 

But let us look at a 3-way used for sorting as with qsort(). A question is where to put NANs? A common goal is to put them at the end is the list - that is all NAN are greater than others. To do so, we need to consistently compare, even if both operands are NANs with different payloads.

// All NAN considered greater than others
// return 0, a positive or negative int.
int fcmp_for_qsort(const void *ap, const void *bp) {
  double a = *(const double *) ap;
  double b = *(const double *) bp;
  if (isnan(a)) {
    if (isnan(b)) {
      return 0;
    }
    return 1;
  }
  if (isnan(b)) -1;
  return (a > b) - (a < b);
} 
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256