【问题标题】:strcmp like interface for comparing numbers in C用于比较 C 中数字的类似 strcmp 的接口
【发布时间】:2021-06-27 10:22:17
【问题描述】:

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

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

我目前使用的是以下宏:

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

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

任何帮助将不胜感激!

【问题讨论】:

  • 这不就是x -y吗?
  • c 支持一种叫做 functions 的东西;)
  • 为什么不简单使用if (x &lt; &lt;y) ...
  • @stark x - y 如果xy 有相反的符号,则可能溢出。此外,它仅适用于那些被提升为int 的无符号类型。
  • 一些针对提出解决方案的人的测试用例:ncmp(0u, 1u)ncmp(MIN_INT, 1)

标签: c precision strcmp arithmetic-expressions tgmath


【解决方案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) &lt; (y)),但其类型用于选择相应的 泛型关联 表达式(如果有)。

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

注意 4:由于在控制表达式中对 &lt; 的操作数进行了通常的算术转换,因此不需要为等级低于 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)

【讨论】:

    【解决方案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));
    }
    

    可以制作更复杂的两阶段_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 精确编码所有longlong double 存在并精确编码所有long 时,将longdouble 都转换为通用类型并进行比较是最简单的。

    【讨论】:

    • 感谢您的全面回答。 cmp_long_double 是我一直在寻找的东西。
    • 比较函数的中间将double转换为`long`以获得精确的同情。如果double 接近long 范围,则先前的比较测试: [LONG_MIN.9999... to LONG_MAX.999...] LONG_MIN 肯定是某个幂=-2(取反),因此比较是准确的。比较 double,避免使用 LONG_MAX`,因为它可能不精确,因为 LONG_MAX 是 2 的幂负 1 并且不能完全表示为 doubleDBL_LONG_MAX_P1 是 2 的精确幂。
    • @JayLee LONG_MAX + 1.0 首先将LONG_MAX 转换为double。如果不准确,“结果是最接近的较高或最近的较低可表示值,以实现定义的方式选择。”。然后添加 1.0(可能对 64 位 long 无效)。使用(LONG_MAX/2 + 1)*2.0 避免实现定义的方式 形成doubleLONG_MAX 多一个。
    • 在我看来,超出关系运算符的限制似乎超出了职责范围。你应该得到一枚奖牌!
    • @JayLee 请注意,引用 there 指的是浮点 FP 到更窄的 FP,这里关注的是整数到 FP。两者都有实现定义的方面需要考虑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多