使用awk / gawk,我需要执行涉及NaN浮点值的数值比较。尽管gawk似乎已正确地将我的用户输入转换为数值NaN(即不是字符串“ NaN”),但是使用运算符'<'或'>'执行的比较结果与我期望的结果不匹配
期望:
诸如x > y
或x < y
之类的比较,其中x为NaN且y为浮点值(包括NaN和+/- Infinity),应评估为false。 [需要引用IEEE文档(但wikipedia NaN有表)]。
实际结果:
NaN <2.0 == 0,但NaN> 2.0 == 1
以下代码段采用第一个字段,并向其中添加0
以强制转换为整数(如in the gnu awk manual所述)。然后,它使用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正确传播的其他方法/选项? 我阅读了standards vs practice上有关NaN的页面,并且我认为我正在正确地做这件事。我感觉到NaN可能不是超级好地融入awk。我找不到一种可靠的方法来测试某个值是否为NaN,例如(通过printf除外)。
答案 0 :(得分:3)
POSIX必须说些什么?
首先,POSIX允许但不要求awk支持NaN
或Inf
值。来自awk IEEE Std 1003.1-2017 POSIX standard:
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 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"
或"info"
完全出乎意料。后者可能是主要原因-当不使用--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"
的dito)。 ..)
("+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)
可以按预期工作(添加了一些注释来说明):
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>
}