使用awk/gawk,我需要执行涉及NaN浮点值的数值比较。尽管gawk似乎正确地将我的用户输入转换为一个数值NaN (即不是字符串"NaN"),但是与操作符'<‘或'>’的比较结果并不符合我的预期。
期望
比较,例如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
是否有不同的方式/选项让NaNs正确地传播?我在标准与实践上读到了关于NaN的那一页,我认为我做得很对。我觉得NaN可能不是很好的烘焙成awk。例如,我找不到一种可靠的方法来测试某个值是否是NaN (除了通过printf)。
发布于 2018-06-27 08:07:29
POSIX必须说什么?首先允许,但不要求awk支持NaN或Inf值。来自美国IEEE Std 1003.1-2017年POSIX标准
awk的历史实现不支持数字字符串中的浮点无穷大和NaNs,例如
"-INF"和"NaN"。但是,使用atof()或strtod()函数进行转换的实现如果使用该函数的ISO/IEC 9899:1999标准版本而不是ISO/IEC 9899:1990标准版本,则会获得对这些值的支持。由于疏忽,该标准的2001年至2004年版本不允许支持无穷大和NaNs,,但是在此修订版中,支持是允许的(但不是必需的)。这是对awk程序行为的无声更改;例如,在POSIX语言环境中,表达式: ("-INF“+0< 0) 以前有值0,因为"-INF"转换为0,但现在可能有值0或"-INF"。
GNU如何处理这些神奇的IEEE数字? GNU awk手册声明:
--posix的情况下,gawk专门解释四个字符串值"+inf"、"-inf"、"+nan“和"-nan",从而产生相应的特殊数字值。--posix命令行选项,gawk就变成了“不要碰”。字符串值直接传递给系统库的strtod()函数,如果它成功地返回一个数值,这就是所使用的。根据定义,结果不能跨不同系统移植。因此,简而言之,GNU -没有--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可能识别字符串"nan"和"inf"以及其他字符串,如"infinity"或完全出乎意料的"nano"或"info"。后者可能是--当不使用--posix时--符号非常重要的主要原因,只有字符串"+nan“、"-nan”、"+inf“和"-inf”才被识别。
GNU是如何对这些神奇的IEEE号码进行排序的?
在深入了解GNU的源代码时,我们发现了对常规cmp_awknums的以下注释
/* *此例程还用于排序数字数组索引或值。*为了排序的目的,NaN被认为大于*任何其他值,并且所有NaN值都被认为是等价的和相等的。*这不符合IEEE标准,但符合w.r.t。NaN *在awk级别上的比较是一个不同的问题,需要在解释器中分别为每个操作码处理。*/
这解释了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 (dito表示"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我们可以确信,在研究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>
}发布于 2022-08-31 09:09:38
下面是我能想到的最简洁的方法(我可以想到)来获得4个特殊值:
gawk 'BEGIN { print _-=log(_<_),-_,_-=_ _,-_ }‘
+inf -inf +nan -nan因此,就指示函数与其数值之间的关系而言--(遗憾的是,NaNs还需要一个字符串比较,因为就awk而言,+nan == -nan是数字的):
函数is_inf(_) {传回is_negINF(_) \x\x}\x{返回-_<+_ & (+_)==-log(_<_) }函数is_negINF(_) {返回+_<&log;&log;&log;(+_)== log(_<_) }函数is_nan(_) {返回is_negNAN(_) \x- is_posNAN(_) }}函数is_posNAN (_ ) { return (_!~"-") && (_)== (_-=_=log(_<_)) }}函数is_negNAN(_) { return (_“-”) && (_)==(-(_-=_=log(_<_)}
1 +inf -inf +nan -nan
2 1 1 0 0 0 0
3 1 0 1 0 0 0
4 0 0 0 1 1 0
5 0 0 0 1 0 1
8 BEGIN { print __=_-=log(_), ___=-_, ____=_-=_, _____=-_
10 print is_inf(__), is_posINF(__), is_negINF(__),
is_nan(__), is_posNAN(__), is_negNAN(__)
… < repeat for the other 3 >
18 }这些方案绝不是包罗万象的;可能包含边缘案例的意外结果,。
https://stackoverflow.com/questions/51056836
复制相似问题