GNU awk(gawk)中涉及NaN的令人惊讶的数值比较结果

时间:2018-06-27 07:24:52

标签: awk floating-point nan

使用awk / gawk,我需要执行涉及NaN浮点值的数值比较。尽管gawk似乎已正确地将我的用户输入转换为数值NaN(即不是字符串“ NaN”),但是使用运算符'<'或'>'执行的比较结果与我期望的结果不匹配

期望

诸如x > yx < 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除外)。

1 个答案:

答案 0 :(得分:3)

POSIX必须说些什么? 首先,POSIX允许但不要求awk支持NaNInf值。来自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,但是现在它可能具有值01

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

如何检查infnan

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>
}