POSIX 有什么要说的?
首先,POSIX 允许但不要求 awk 支持 NaN 或 Inf 值。来自awk IEEE Std 1003.1-2017 POSIX standard:
awk 的历史实现不支持数字字符串中的浮点无穷大和 NaN;例如,"-INF" 和 "NaN"。但是,如果使用函数的 ISO/IEC 9899:1999 标准版本而不是 ISO/IEC 9899:1990 标准版本,则使用 atof() 或 strtod() 函数进行转换的实现会支持这些值.由于疏忽,该标准的 2001 至 2004 版本不允许支持无穷大和 NaN,但在此修订版中,允许(但不是必需)支持。这是对 awk 程序行为的无声改变;例如,在 POSIX 语言环境中,表达式:
("-INF" + 0 < 0)
以前的值是0,因为"-INF" 转换为0,但现在它的值可能是0 或1。
GNU awk 如何处理如此神奇的 IEEE 数字?GNU awk manual 声明:
- 如果没有
--posix,gawk 会特别解释四个字符串值"+inf"、"-inf"、"+nan"和"-nan",产生相应的特殊数值。前导符号作为gawk (和用户)该值确实是数字。
- 使用
--posix 命令行选项,gawk 变成“放手”。字符串值直接传递给系统库的strtod()函数,如果成功返回一个数值,就使用这个。 根据定义,结果不能跨不同系统移植。
因此,简而言之,GNU awk(没有 --posix 选项)仅能够成功转换字符串“+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" 或 @ 987654360@。后者可能是主要原因——当不使用--posix 时——符号是最重要的,只有字符串“+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.
*/
这解释了 OP 的原始问题,为什么 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”被视为无穷大,而在没有--posix 的情况下,它将被转换为ZERO("inf"、"nan"、...的同上)
("+nan" < 2) 和 ("+nan"+0 < 2) 是什么关系?
在第一种情况下,进行纯字符串比较,而在第二种情况下,字符串被强制为数字并进行数字比较。这类似于("2.0" == 2) 和("2.0"+0 == 2)。第一个返回false,第二个返回true。这种行为的原因是,在第一种情况下,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
在查看cmp_awknums的源码时,我们可以确信isnan(x)函数可以正常工作(添加了一些cmets来解释):
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>
}