数字比较在awk中产生不正确的结果

问题描述 投票:4回答:1

我最近在网站上找到了一个脚本:

bash, find nearest next value, forward and backward

这是相对陈旧的,需要50个代表评论,我没有。我试图让它工作,并且不太了解awk语法,但我正在尝试。在我正在使用的测试文件中:

 -3.793  0.9804E+00  0.3000E+02
 -3.560  0.1924E-01  0.3000E+02
 -3.327  0.3051E-04  0.3000E+02
 -3.093  0.3567E-08  0.3000E+02
 -2.860  0.3765E-06  0.3000E+02
 -2.627  0.1119E-02  0.3000E+02
 -2.394  0.2520E+00  0.3006E+02

这是脚本:

{
if ($fld > tgt) {
    del = $fld - tgt
    if ( (del < minGtDel) || (++gtHit == 1) ) {
        minGtDel = del
        minGtVal = $fld
    }
}
else if ($fld < tgt) {
    del = tgt - $fld
    if ( (del < minLtDel) || (++ltHit == 1) ) {
        minLtDel = del
        minLtVal = $fld
    }
}
else {
    minEqVal = $fld
}
}
END {
print (minGtVal == "" ? "NaN" : minGtVal)
print (minLtVal == "" ? "NaN" : minLtVal)
}

当这样运行时:

$ awk -v fld=1 -v tgt=-3 -f awk DOSCAR

生产:

 -2.860
 NaN

即使有一个下限,我也不太确定如何解决它。原帖没有负数,所以他们没有这个问题。任何帮助表示赞赏。

awk numeric
1个回答
6
投票

输入文件中有一个空行,触发了经典的awk陷阱。

核心问题是awk比较运算符的奇怪行为,它不需要您指定是否需要数字或字符串比较。 (<意见>这正是为什么自动比较算子是一个坏主意。</ opinion>)

简而言之,awk中有三种标量类型:数字,字符串和“数字字符串”。程序中的文字是数字或字符串,算术运算符的结果总是一个数字,而字符串连接的结果总是一个字符串。但是你所比较的值 - $fldtgt--都是潜在的“数字字符串”,因为它们来自用户输入。

“数字字符串”是来自用户输入的字符串,恰好“看起来像”一个数字。总的来说,“看起来像一个数字”的定义并不令人惊讶,除了一个细节:空字符串不计算。

如果比较两个数字,则比较是数字。如果比较两个字符串,则比较是字典。但是,如果您要比较的一个(或两个)可能是“数字字符串”,那么比较的类型取决于它是否实际上是“数字字符串”。如果它是“数字字符串”,它会变成一个数字;否则,另一个值变成一个字符串。

因此,如果$fld是一个空字符串,那么将它与tgt进行比较将是字符串比较而不是数字比较。空字符串是字符串比较的最小可能字符串,因此它会变小。但是,当你计算$fld - tgt时,$fld将被强制转换为数字,在这种情况下,空字符串变为0。

所以有两种可能性。最简单的方法是强制将$fld改为数字;这至少是一致的:

{
    val = $fld + 0
    if (val > tgt) {
        del = val - tgt
        if ( (del < minGtDel) || (++gtHit == 1) ) {
            minGtDel = del
            minGtVal = val
        }
    }
    else if (val < tgt) {
        del = tgt - val
        if ( (del < minLtDel) || (++ltHit == 1) ) {
            minLtDel = del
            minLtVal = val
        }
    }
    else {
        minEqVal = val
    }  
}
END {
    print (minGtVal == "" ? "NaN" : minGtVal)
    print (minLtVal == "" ? "NaN" : minLtVal)
}

另一种方法是消除指示字段不能是数字的行。对数值进行简单且通常可靠的测试是将值与自身进行比较,并将其作为一个数字进行比较:

(val = $fld + 0) == $fld {
    if (val > tgt) {
        del = val - tgt
        if ( (del < minGtDel) || (++gtHit == 1) ) {
            minGtDel = del
            minGtVal = val
        }
    }
    else if (val < tgt) {
        del = tgt - val
        if ( (del < minLtDel) || (++ltHit == 1) ) {
            minLtDel = del
            minLtVal = val
        }
    }
    else {
        minEqVal = val
    }  
}
END {
    print (minGtVal == "" ? "NaN" : minGtVal)
    print (minLtVal == "" ? "NaN" : minLtVal)
}
© www.soinside.com 2019 - 2024. All rights reserved.