strcmp как интерфейс для сравнения чисел в C

я хочу сделать strcmp-подобный интерфейс для сравнения чисел, например, ncmp(x, y) который возвращает int > 0 если x > y, 0 если x = y, < 0 если x < y в C (не C ++).

Хотя я обязательно не хочу ограничивать типы, мой главный интерес - сравнить s и s. "Интерфейс" может быть макросом, как в tgmath.h, или это может быть (набор) функций. Я хочу все пары signed long int и double работать; (signed long int, double) и (double, double) должен работать, например.

Сейчас я использую следующий макрос:

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

Есть ли в этом наивном макросе подводные камни? Есть ли лучшее и надежное решение для сравнения чисел?

Любая помощь будет принята с благодарностью!

2 ответа

Решение

Я хочу все пары signed long int и работать;

Начиная с C11, код можно использовать для управления выбором функции в зависимости от типа.

      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, double и double, long. Я оставлю эту часть OP.

Функция сравнения будет похожа на приведенную ниже. Сложность состоит в том, чтобы не потерять точность (она может быть 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;
}

Могут существовать упрощения.


Когда кодирует все точно или когда long double существует и кодирует все точно, проще всего преобразовать как long и double к общему типу и сравните.

Для этого макроса:

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

основные проблемы:

  1. Для формирования основного выражения требуется дополнительный набор скобок в раскрытии. В раскрытии недостаточно скобок, чтобы превратить его в основное выражение. Должен быть:

            #define ncmp(x, y) (((x) > (y)) - ((x) < (y)))
    
  2. Он оценивает и дважды, что может быть проблемой, если оценка имеет побочные эффекты.

Чтобы избежать проблемы множественной оценки, расширение макроса может использовать универсальное выражение выбора для вызова другой функции для каждого сравниваемого типа.

Примечание 1: общий выбор был добавлен в версию стандарта C 2011 года (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)
Другие вопросы по тегам