Search code examples
cprecisionstrcmparithmetic-expressionstgmath

strcmp like interface for comparing numbers in C


I want to make strcmp-like interface for comparing numbers, e.g., ncmp(x, y) that returns an int > 0 if x > y, 0 if x = y, < 0 if x < y in C (not C++).

Although I necessarily do not want to constrain the types, my main interest is to compare signed long ints and doubles. The 'interface' can be a macro as in tgmath.h, or it can be a (set of) functions. I want all pairs of signed long int and double to work; (signed long int, double) and (double, double) should work for instance.

What I'm currently using is the following macro:

#define ncmp(x, y) ((x) > (y)) - ((x) < (y))

Does this naive macro have any pitfalls? Is there a better, robust solution to compare numbers?

Any help would be greatly appreciated!


Solution

  • I want all pairs of signed long int and double to work;

    Since C11, code can use _Generic to steer function selection based on type.

    int cmp_long(long x, long y) {
      return (x > y) - (x < y);
    }  
    
    int cmp_double(double x, double y) {
      return (x > y) - (x < y);
    }  
    
    #define cmp(X, Y) _Generic((X) - (Y), \
        long: cmp_long((X),(Y)), \
        double: cmp_double((X),(Y)) \
    )
    

    This approach does not well detect cases where X, Y are of different types as the (X) - (Y) uses the common type between them @Ian Abbott. Yet it is a start.

    int main(void) {
      printf("%d\n", cmp(1L, 2L));
      printf("%d\n", cmp(3.0, 4.0));
    }
    

    A more complex 2-stage _Generic could be made to distinguish long, double and double, long. I'll leave that part to OP.

    The compare function would be like the one below. The tricky part is to not lose precision of long (it might be 64-bit) when comparing to double.

    // TBD: handling of NANs
    #define DBL_LONG_MAX_P1 ((LONG_MAX/2 + 1)*2.0)
    int cmp_long_double(long x, double y) {
      // These 2 compares are expected to be exact - no rounding
      if (y >= DBL_LONG_MAX_P1) return -1;
      if (y < (double)LONG_MIN) return 1;
    
      // (long) y is now in range of `long`.  (Aside from NANs)
      long y_long = (long) y; // Lose the fraction
      if (y_long > x) return -1;
      if (y_long < x) return 1;
     
      // Still equal, so look at fraction
      double whole;
      double fraction = modf(y, &whole);
      if (fraction > 0.0) return -1;
      if (fraction < 0.0) return 1;
      return 0;
    }
    

    Simplifications may exist.


    When double encodes all long exactly or when long double exists and encodes all long exactly, it's easiest to convert both the long and double to the common type and compare.