【问题标题】:Numeric comparisons yielding incorrect result in awk数值比较在 awk 中产生不正确的结果
【发布时间】:2019-05-02 05:32:13
【问题描述】:

我最近在网站上发现了一个脚本:

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 脚本——里面根本没有 bash,而且问题不应该有 bash 标记。 (想想如果有人问“我如何在 bash 中做 X”,并得到一个使用 python 的答案——关于该答案的后续问题是 python 问题,而不是 bash 问题)。
  • 顺便说一句,这个问题写得并不理想——您还应该展示您的预期输出,并描述实际输出错误的原因。

标签: awk numeric


【解决方案1】:

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

核心问题是 awk 比较运算符的奇怪行为,它不需要您指定是要进行数字比较还是字符串比较。 (这正是为什么自动比较运算符是一个坏主意的原因。

简而言之,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)
}

【讨论】:

  • 很好的答案,谢谢!我的大测试文件末尾确实有一个空格,很好!没有意识到这就是 awk 的工作方式。 +1
猜你喜欢
  • 2023-03-28
  • 2023-03-20
  • 1970-01-01
  • 2020-12-14
  • 2015-10-12
  • 2015-01-06
  • 1970-01-01
  • 2012-10-30
  • 1970-01-01
相关资源
最近更新 更多