Что говорит 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>
}