类似 strcmp 的接口,用于比较 C 中的数字

strcmp like interface for comparing numbers in C

我想制作类似 strcmp 的界面来比较数字,例如 ncmp(x, y) that returns an int > 0 if x > y, 0 if x = y, < 0 if x < y in C (not C++).

虽然我一定不想限制类型,但我的主要兴趣是比较 signed long intdouble。 'interface' 可以是 tgmath.h 中的宏,也可以是(一组)函数。我希望所有 signed long intdouble 对都能工作;例如,(signed long int, double)(double, double) 应该可以工作。

我目前使用的是下面的宏:

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

这个天真的宏有什么陷阱吗? 是否有更好、更强大的解决方案来比较数字?

如有任何帮助,我们将不胜感激!

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

从 C11 开始,代码可以使用 _Generic 根据类型控制函数选择。

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)) \
)

这种方法不能很好地检测出 X, Y 属于不同类型的情况,因为 (X) - (Y) 使用它们之间的公共类型 @Ian Abbott。然而这是一个开始。

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

可以制作更复杂的 2 阶段 _Generic 来区分 long, doubledouble, long。我会把那部分留给 OP。

比较函数如下所示。棘手的部分是在与 double.

比较时不丢失 long 的精度(它可能是 64 位)
// 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;
}

可能存在简化。


double 准确编码所有 long 或当 long double 存在并准确编码所有 long 时,将 longdouble与普通类型比较。

对于这个宏:

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

主要问题是:

  1. 展开式中需要一组额外的括号才能形成主要表达式 expression.There 展开式中的括号不足以将其变成主要表达式。应该是:

    #define ncmp(x, y) (((x) > (y)) - ((x) < (y)))
    
  2. 它评估了 (x)(y) 两次,如果评估有副作用,这可能是一个问题。

为避免多重计算的问题,宏扩展可以使用通用选择表达式为每个被比较的类型调用不同的函数。

Note 1: generic selection was added in the 2011 version of the C standard (C11).)

这是一个使用泛型选择的示例宏。它可能需要扩展以支持其他类型:

#define ncmp(x, y) _Generic((x) < (y), \
    int: ncmp_si,                      \
    unsigned: ncmp_ui,                 \
    long: ncmp_sli,                    \
    unsigned long: ncmp_uli,           \
    long long: ncmp_slli,              \
    unsigned long long: ncmp_ulli,     \
    float: ncmp_f,                     \
    double: ncmp_d,                    \
    long double: ncmp_ld               \
    )((x), (y))

Note 2: The controlling expression of the generic selection ((x) < (y)) is not evaluated, but its type is used to select a corresponding generic association expression (if any).

Note 3: The choice of < in the controlling expression does not matter much, but it does at least check that (x) and (y) have an ordered relationship. For arithmetic operands, the type of the controlling expression is the result of the usual arithmetic conversions.

Note 4: Due to the usual arithmetic conversions done to the operands of < in the controlling expression, there is no need to add cases for integer types below the rank of int.

Note 5: It is possible to add a default: generic association. For example, it could be defined to fall back to using the less safe multiple evaluation method as follows:

#define ncmp(x, y) _Generic((x) < (y),         \
    int: ncmp_si((x), (y)),                    \
    unsigned: ncmp_ui((x), (y)),               \
    long: ncmp_sli((x), (y)),                  \
    unsigned long: ncmp_uli((x), (y)),         \
    long long: ncmp_slli((x), (y)),            \
    unsigned long long: ncmp_ulli((x), (y)),   \
    float: ncmp_f((x), (y)),                   \
    double: ncmp_d((x), (y)),                  \
    long double: ncmp_ld((x), (y)),            \
    default: ((x) > (y)) - ((x) < (y))         \
    )

but I chose to leave it up to the programmer to add the missing cases.

有必要定义上面每个通用关联所使用的功能。为了节省一些输入,可以定义一个辅助宏来定义它们:

#define MK_NCMP_(suf, T) \
static inline int ncmp_##suf(T x, T y) { return (x > y) - (x < y); }

MK_NCMP_(si, int)
MK_NCMP_(ui, unsigned)
MK_NCMP_(sli, long)
MK_NCMP_(uli, unsigned long)
MK_NCMP_(slli, long long)
MK_NCMP_(ulli, unsigned long long)
MK_NCMP_(f, float)
MK_NCMP_(d, double)
MK_NCMP_(ld, long double)