Как сравнить длинные удвоения с qsort() и в отношении не-числа ?

При сортировке массива, который может содержать не числа, я хотел бы поместить все эти NAN в один конец отсортированного массива.


qsort() накладывает некоторые ограничения на функцию сравнения.

Функция должна возвращать целое число, меньшее, равное или больше нуля, если первый аргумент считается соответственно меньше, равным или большим, чем второй.
C11dr §7.22.5.2 3

Если одни и те же объекты ... передаются функции сравнения более одного раза, результаты должны быть согласованы друг с другом. То есть для qsort они должны определить общий порядок в массиве, ... один и тот же объект всегда должен одинаково сравниваться с ключом.
§7.22.5 4

a > b ложно, когда a <= b или если a не является числом, или если b не является числом. Таким образом, a > b не то же самое, что !(a <= b), поскольку они имеют противоположные результаты, если один из них - NaN.

Если функция сравнения использует return (a > b) - (a < b);, код вернет 0, если один или оба a или b равны NaN. Массив не будет отсортирован должным образом и теряет требование полного упорядочивания .

Аспект long double этой сортировки важен при использовании таких функций классификации, как int isnan(real-floating x); или int isfinite(real-floating x);. Я знаю, что isfinite( finite_long_double_more_than_DBL_MAX) может вернуть false. Поэтому меня беспокоит, что isnan(some_long_double) может сделать что-то неожиданно.


Я попробовал следующее. Видимо, сортирует по желанию.

Подвопрос: Достаточно ли compare() ниже для сортировки по желанию? Какие-нибудь рекомендуемые упрощения? Если нет - как исправить? (Для этой задачи допустима сортировка значений типа 0,0L и -0,0L любым способом)

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <float.h>

int compare(const void *a, const void *b) {
  const long double *fa = (const long double *) a;
  const long double *fb = (const long double *) b;
  if (*fa > *fb) return 1;
  if (*fa < *fb) return -1;

  if (*fa == *fb) {
    //return -memcmp(fa, fb, sizeof *fa); if -0.0, 0.0 order important.
    return 0;
  }
  // At least one of *fa or *fb is NaN
  // is *fa a non-NaN?
  if (!isnan(*fa)) return -1;
  if (!isnan(*fb)) return 1;

  // both NaN
  return 0;
  // return -memcmp(fa, fb, tbd size); if NaN order important.
}

int main(void) {
  long double x[] = { 0.0L / 0.0, 0.0L / 0.0, 0.0, 1.0L / 0.0, -0.0, LDBL_MIN,
      LDBL_MAX, 42.0, -1.0L / 0.0, 867-5309, -0.0 };
  x[0] = -x[0];
  printf("unsorted: ");
  size_t n = sizeof x / sizeof x[0];
  for (size_t i = 0; i < n; i++) {
    printf("%.3Le,", x[i]);
  }
  printf("\nsorted: ");
  qsort(x, n, sizeof x[0], compare);
  for (size_t i = 0; i < n; i++) {
    printf("%.3Le,", x[i]);
  }
  puts("");
}

Выход

unsorted: nan,-nan,0.000e+00,inf,-0.000e+00,3.362e-4932,1.190e+4932,4.200e+01,-inf,-4.442e+03,-0.000e+00,
sorted: -inf,-4.442e+03,-0.000e+00,0.000e+00,-0.000e+00,3.362e-4932,4.200e+01,1.190e+4932,inf,nan,-nan,

Если бы я знал, что функция сравнения правильная, я бы опубликовал в Code Review идеи по улучшению. Однако я недостаточно уверен, что код правильно работает с этими надоедливыми NaN.

6
chux - Reinstate Monica 3 Янв 2018 в 02:48

2 ответа

Лучший ответ

Это просто переупорядочивание ваших тестов, но оно делает статус NaN более ясным, если хотите.

int compare(const void *a, const void *b)
{
    const long double fa = *(const long double *) a;
    const long double fb = *(const long double *) b;

    if (isnan(fa))
    {
        if (isnan(fb))
        {
            return 0;
        }
        return 1;
    }
    if (isnan(fb))
    {
        return -1;
    }
    if (fa > fb) return 1;
    if (fa < fb) return -1;

    /* no more comparisons needed */
    return 0;
}

Поскольку тесты для NaN находятся вверху и никакие NaN не должны проходить, три нижние строки можно смело заменить на ваши

return (a > b) - (a < b);

Помимо обсуждения различных типов NaN (немного похоже на то, сколько ангелов может танцевать на ядре ЦП), это должно быть достаточно стабильно для ваших целей, и я могу Не вижу возможных проблем с этим кодом.

С Clang ни -ffast-math, ни -fdenormal-fp-math=[ieee|preserve-sign|positive-zero] не дают других результатов. Как и gcc с -ffast-math, -funsafe-math-optimizations и даже -ffinite-math-only (последнее, скорее всего, потому, что нет других операций, кроме прямого сравнения с NaN).

Для полноты картины я протестировал как std::numeric_limits<double>::signaling_NaN();, так и std::numeric_limits<double>::quiet_NaN(); (из C ++ <limits.h>) - опять же, без разницы в порядке сортировки.

5
usr2564301 3 Янв 2018 в 01:00

Тест NaN

int isnan(real-floating x);

Макрос isnan определяет, является ли значение его аргумента NaN. Во-первых, аргумент, представленный в формате, превышающем его семантический тип, преобразуется в его семантический тип. Тогда определение основывается на типе аргумента. 235
235 Для макроса isnan тип определения не имеет значения, если реализация не поддерживает NaN в типе оценки, но не в семантическом типе.

isnan(some_long_double) будет работать, как и предполагалось, за исключением редкой платформы.

int isunordered(real-floating x, real-floating y) действует как isnan(), ожидая, что он учитывает оба аргумента.

На многих платформах код может использовать (a == a) в качестве кандидата NaN-теста, поскольку он оценивает 0, когда a равен NaN, и 1 в противном случае. К сожалению, если реализация не определяет __STDC_IEC_559__, это не обязательно сработает.


Сравнение
>=, >, <, <= и C11 7.12.14 Макросы сравнения

Использование >=, >, <, <=, когда хотя бы один операнд - NaN, может привести к "недопустимой" исключительной ситуации с плавающей запятой. Поэтому предварительное тестирование для NaN целесообразно, как ответил @ usr2564301

C предлагает макросы isgreaterequal(), isgreaterequal(), isless(), islessthna(), которые выполняют сравнение и не вызывают "недопустимую" плавающую точку исключение. Это хорошая альтернатива для double, но в макросах используется плавающее , которое может отличаться от long double. isgreater(long_double_a, long_double_a) может оцениваться как double и не обеспечивать желаемый результат сравнения.

Проблема с макросами classify заключается в том, что семантический тип может быть уже, чем long double.


Следующее использует вышеупомянутые идеи, и, как я читал, спецификация C хорошо определена и функционально верна для всех случаев, кроме редкого: когда long double имеет NaN, но не вещественное плавающее (часто double) нет.

#include <math.h>

// compare 2 long double.  All NaN are greater than numbers.
int compare(const void *a, const void *b) {
  const long double *fa = (const long double *) a;
  const long double *fb = (const long double *) b;

  if (!isunordered(*fa, *fb)) {
    return (*fa > *fb) - (*fa < *fb);
  }

  if (!isnan(*fa)) {
    return -1;
  }
  return isnan(*fb);  // return 0 or 1
}

Примечание. Прочитав множество хороших комментариев и много узнав, я отправляю этот самостоятельный ответ, как указано в Могу ли я ответить на свой вопрос? в дополнение к принятию другого ответа.

2
chux - Reinstate Monica 4 Янв 2018 в 00:15