0

我想制作strcmp类似的接口来比较数字,例如,在 C(不是 C++)ncmp(x, y)中返回一个 int > 0if x > y0if x = y< 0if 。x < y

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

我目前使用的是以下宏:

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

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

任何帮助将不胜感激!

4

2 回答 2

2

我希望所有对signed long intdouble工作;

自 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。

比较功能将如下所示。棘手的部分是longdouble.

// 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存在并且完全编码时,将and都转换为通用类型并进行比较long是最简单的。longdouble

于 2021-03-31T12:32:12.610 回答
1

对于这个宏:

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

主要问题是:

  1. 它需要在展开式中附加一组括号来形成主表达式。展开式中没有足够的括号将其变成主表达式。它应该是:

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

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

注 1:2011 版 C 标准 (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))

注 2:(x) < (y)不计算泛型选择 ( ) 的控制表达式,但其类型用于选择相应的泛型关联表达式(如果有)。

注3:<控制表达式中的选择无关紧要,但它至少检查(x)(y)具有有序关系。对于算术操作数,控制表达式的类型是通常算术转换的结果。

注 4:由于<在控制表达式中对 的操作数进行了通常的算术转换,因此不需要为 的等级以下的整数类型添加案例int

注 5:可以添加default:通用关联。例如,可以定义回退到使用不太安全的多重评估方法,如下所示:

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

但我选择让程序员来添加缺失的案例。

有必要定义上面每个通用关联所使用的函数。为了节省一点打字,可以定义一个辅助宏来定义它们:

#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)
于 2021-03-31T13:50:46.093 回答