Удивительные результаты численного сравнения с участием NaN в GNU awk (gawk) - PullRequest
0 голосов
/ 27 июня 2018

Используя awk / gawk, мне нужно выполнить числовые сравнения с использованием значений с плавающей запятой NaN. Несмотря на то, что gawk, похоже, правильно преобразовал мой пользовательский ввод в числовое значение NaN (т.е. не в строку «NaN»), результаты сравнений, выполненных с операторами «<» или «>», не соответствуют ожидаемым .

Expectation

Сравнения, такие как x > y или x < y, где x - это NaN, а y - это значение с плавающей запятой (включая NaN и +/- Infinity), должно принимать значение false. [необходима ссылка на документ IEEE (но Википедия NaN имеет таблицу)].

Фактический результат:

NaN <2.0 == 0, но NaN> 2.0 == 1

Следующий фрагмент занимает первое поле и добавляет к нему 0, чтобы принудительно преобразовать в целое число (как описано в руководстве по gnu awk ). Затем он использует printf, чтобы показать тип переменных и выражений (моя конкретная версия gawk не имеет typeof()).

$ echo -e "+nan\n-nan\nfoo\nnanny" | awk \
'{x=($1+0); printf "%s: float=%f str=%s x<2==%f x>2==%f\n",$1,x,x,(x<2.0),(x>2.0);}'

+nan: float=nan str=nan x<2==0.000000 x>2==1.000000
-nan: float=nan str=nan x<2==0.000000 x>2==1.000000
foo: float=0.000000 str=0 x<2==1.000000 x>2==0.000000
nanny: float=0.000000 str=0 x<2==1.000000 x>2==0.000000

$ echo -e "+nan\n-nan\nfoo\nnanny" | awk --posix \
'{x=($1+0); printf "%s: float=%f str=%s x<2==%f x>2==%f\n",$1,x,x,(x<2.0),(x>2.0);}'           

+nan: float=nan str=nan x<2==0.000000 x>2==1.000000
-nan: float=nan str=nan x<2==0.000000 x>2==1.000000
foo: float=0.000000 str=0 x<2==1.000000 x>2==0.000000
nanny: float=nan str=nan x<2==0.000000 x>2==1.000000

Запуск GNU Awk 4.1.3, API: 1.1

Есть ли другой способ / возможность правильного распространения NaN? Я прочитал страницу о стандартах против практики , в которой говорится о NaN, и я думаю, что я делаю это правильно. Я чувствую, что NaN, возможно, не очень хорошо запекается в awk. Я не смог найти надежный способ проверить, является ли значение, например, NaN (кроме как через printf).

1 Ответ

0 голосов
/ 27 июня 2018

Что говорит POSIX? Прежде всего, POSIX допускает, но не требует, чтобы awk поддерживал значения NaN или Inf. Из стандарта awk IEEE Std 1003.1-2017 POSIX :

Исторические реализации awk не поддерживали бесконечности с плавающей точкой и NaN в числовых строках; например, "-INF" и "NaN". Однако реализации, использующие функции atof() или strtod() для выполнения преобразования, получили поддержку этих значений, если они использовали стандартную версию ISO / IEC 9899: 1999 функция вместо стандартной версии ISO / IEC 9899: 1990. Из-за недосмотра в редакциях этого стандарта 2001–2004 гг. Не допускалась поддержка бесконечностей и NaN, , но в этой редакции поддержка разрешена (но не обязательна) . Это тихое изменение в поведении программ awk; например, в локали POSIX выражение:

("-INF" + 0 < 0)

ранее имело значение 0, потому что "-INF" преобразовано в 0, но теперь может иметь значение 0 или 1.

Как GNU awk обрабатывает такие магические числа IEEE? В руководстве GNU awk говорится:

  • Без --posix, gawk интерпретирует четыре строковых значения "+inf", "-inf", "+nan"-nan" специально, создавая соответствующие специальные числовые значения. Ведущий знак действует как сигнал для gawk ( и пользователь), что значение действительно числовое.
  • С параметром командной строки --posix gawk становится "hands off" . Строковые значения передаются непосредственно в функцию strtod() системной библиотеки, и если она успешно возвращает числовое значение, это то, что используется. По определению, результаты не переносимы в разных системах.

Короче говоря, GNU awk & mdash; без опции --posix & mdash; только может успешно преобразовать строки "+ nan", "-nan", "+ inf" и "-inf" в представление с плавающей запятой (см. функцию is_ieee_magic_val).

Удивительно, но он не конвертирует "nan" и "inf", тем более что преобразование строки "+nan"+0 является беззнаковым "nan"

$ gawk 'BEGIN{print "+nan"+0, "nan"+0}'
nan 0

Примечание: при использовании --posix, GNU awk может распознавать строки "nan" и "inf", а также другие, такие как "infinity" или совершенно неожиданные, "nano" или "info". Последнее, вероятно, является основной причиной, по которой & mdash; когда не используется --posix & mdash; знак имеет первостепенное значение, и распознаются только строки "+ nan", "-nan", "+ inf" и "-inf".

Как GNU awk сортирует такие магические числа IEEE?

При копании в исходный код GNU awk, мы находим следующий комментарий для подпрограммы cmp_awknums:

/*
 * This routine is also used to sort numeric array indices or values.
 * For the purposes of sorting, NaN is considered greater than
 * any other value, and all NaN values are considered equivalent and equal.
 * This isn't in compliance with IEEE standard, but compliance w.r.t. NaN
 * comparison at the awk level is a different issue and needs to be dealt
 * within the interpreter for each opcode separately.
 */

Это объясняет первоначальный вопрос ОП, почему NaN не следует сравнениям IEEE, и поэтому ("+nan"+0<2) равно 0 (false), а ("+nan"+0>2) равно 1 (true). (Примечание: мы добавили ноль в строку, чтобы обеспечить преобразование чисел)

Это можно продемонстрировать с помощью следующего кода (без --posix):

BEGIN { s = "1.0 +nan 0.0 -1 +inf -0.0 1 1.0 -nan -inf 2.0"; split(s, a)
        PROCINFO["sorted_in"] = "@val_num_asc"
        for (i in a) printf a[i] OFS; printf "\n"
        PROCINFO["sorted_in"] = "@val_num_desc"
        for (i in a) printf a[i] OFS; printf "\n"
      }

выводит следующие порядки:

-inf -1 -0.0 0.0 1 1.0 1.0 2.0 +inf +nan -nan
-nan +nan +inf 2.0 1.0 1.0 1 0.0 -0.0 -1 -inf

Если NaN будет следовать соглашениям IEEE, оно всегда должно появляться в начале списка, независимо от порядка, но это явно не так. То же самое верно при использовании --posix:

function arr_sort(arr,   x, y, z) {
  for (x in arr) { y = arr[x]; z = x - 1
     # force numeric comp
     while (z && arr[z]+0 > y+0) { arr[z + 1] = arr[z]; z-- }
    arr[z + 1] = y
  }
}
BEGIN { s = "1.0 +nan 0.0 -1 +inf -0.0 1 1.0 -nan -inf 2.0"
        s = s" inf nan info -infinity"; split(s, a)
       arr_sort(a)
       for (i in a) printf a[i] OFS; printf "\n"   
}
-inf -infinity -1 0.0 -0.0 1.0 1 1.0 2.0 +inf inf info +nan -nan nan 

Обратите внимание, что строка "info" рассматривается как бесконечность, а & mdash; без --posix & mdash; оно будет преобразовано в ZERO (dito для "inf", "nan", ...)

Что такое сделки с ("+nan" < 2) и ("+nan"+0 < 2)?

В первом случае выполняется чистое сравнение строк, тогда как во втором случае строка переводится в число и выполняется числовое сравнение. Это похоже на ("2.0" == 2) и ("2.0"+0 == 2). Где первый возвращает ложь, второй возвращает истину. Причина такого поведения заключается в том, что в первом случае awk знает только, что «2.0» является строкой, он не проверяет ее содержимое, поэтому он преобразует 2 в строку.

BEGIN { print ("-nan" < 2)  , ("-nan" > 2)  , ("+nan" < 2)  , ("+nan" > 2)
        print ("-nan"+0 < 2), ("-nan"+0 > 2), ("+nan"+0 < 2), ("+nan"+0> 2)
        print ("-nan"+0 )   , ("-nan"+0)    , ("+nan"+0)    , ("+nan"+0)   }
1 0 1 0
0 1 0 1
nan nan nan nan

Как проверить inf или nan:

function isnum(x) { return x+0 == x }
function isnan(x) { return (x+0 == "+nan"+0) }
function isinf(x) { return ! isnan(x) && isnan(x-x)  }
BEGIN{inf=log(0.0);nan=sqrt(-1.0);one=1;foo="nano";
    print "INF",   inf , isnum(inf)   , isnan(inf)   , isinf(inf)
    print "INF",  -inf , isnum(-inf)  , isnan(-inf)  , isinf(-inf)
    print "INF", "+inf", isnum("+inf"), isnan("+inf"), isinf("+inf")
    print "INF", "-inf", isnum("-inf"), isnan("-inf"), isinf("-inf")
    print "NAN",   nan , isnum(nan)   , isnan(nan)   , isinf(nan)
    print "NAN",  -nan , isnum(-nan)  , isnan(-nan)  , isinf(-nan)
    print "NAN", "+nan", isnum("+nan"), isnan("+nan"), isinf("+nan")
    print "NAN", "-nan", isnum("-nan"), isnan("-nan"), isinf("-nan")
    print "ONE",   one , isnum(one)   , isnan(one)   , isinf(one)
    print "FOO",   foo , isnum(foo)   , isnan(foo)   , isinf(foo)
}

Возвращает:

INF -inf 1 0 1
INF inf 1 0 1
INF +inf 1 0 1
INF -inf 1 0 1
NAN -nan 1 1 0
NAN nan 1 1 0
NAN +nan 1 1 0
NAN -nan 1 1 0
ONE 1 1 0 0
FOO nano 0 0 0

Мы можем быть уверены, что функция isnan(x) работает, как и ожидалось, при исследовании исходного кода cmp_awknums (добавлено несколько комментариев для объяснения):

int cmp_awknums(const NODE *t1, const NODE *t2)
{
    // isnan is here the C version
    // this ensures that all NANs are equal
    if (isnan(t1->numbr))
        return ! isnan(t2->numbr);
    // this ensures that all NANs are bigger than any other number
    if (isnan(t2->numbr))
        return -1;
    // <snip>
}
...