awk高精度算术

awk高精度算术

我正在寻找一种方法来告诉 awk 在替换操作中进行高精度算术。这涉及从文件中读取字段并用该值的 1% 增量替换它。然而,我在那里失去了精度。这是问题的简化再现:

 $ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {print}'
   0.546748

这里,我有一个小数点后 16 位的精度,但 awk 只给出了 6 位。使用 printf,我得到相同的结果:

$ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}'
0.546748

关于如何获得所需的精度有什么建议吗?

答案1

$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g '{gsub($1, $1*1.1)}; {print}'
0.54674805518902947

或者更确切地说在这里:

$ echo 0.4970436865354813 | awk '{printf "%.17g\n", $1*1.1}'
0.54674805518902947

可能是您能实现的最好目标。用于bc任意精度。

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943

答案2

为了使用 (GNU) awk(编译了 bignum)获得更高的精度,请使用:

$ echo '0.4970436865354813' | awk -M -v PREC=100 '{printf("%.18f\n", $1)}'
0.497043686535481300

PREC=100 表示 100 位,而不是默认的 53 位。
如果 awk 不可用,请使用 bc

$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943

或者您需要学会忍受浮动固有的不精确性。


在您的原始行中有几个问题:

  • 系数 1.1 表示增加 10%,而不是 1%(应该是 1.01 乘数)。我会用10%。
  • 从字符串到(浮点)数字的转换格式由 CONVFMT 给出。它的默认值为%.6g。这将值限制为 6 位十进制数字(点后)。这适用于 的 gsub 更改的结果$1

    $ a='0.4970436865354813'
    $ echo "$a" | awk '{printf("%.16f\n", $1*1.1)}'
    0.5467480551890295
    
    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.16f\n", $1)}'
    0.5467480000000000
    
  • printf 格式g删除尾随零:

    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.16g\n", $1)}'
    0.546748
    
    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.17g\n", $1)}'
    0.54674800000000001
    

    这两个问题都可以通过以下方式解决:

    $ echo "$a" | awk '{printf("%.17g\n", $1*1.1)}'
    0.54674805518902947
    

    或者

    $ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}'
    0.54674805518902947 
    

但不要以为这意味着更高的精度。内部数字表示形式仍然是双倍大小的浮点数。这意味着 53 位精度,因此您只能确定 15 位正确的十进制数字,即使很多时候最多 17 位数字看起来是正确的。那是海市蜃楼。

$ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}'
0.546748055189029469325134868996

正确的值为:

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943

如果 bignum 库已编译为以下版本,也可以使用 (GNU) awk 进行计算:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000

答案3

我的 awk 脚本不仅仅是一行代码,因此我结合了 Stéphane Chazelas 和 Isaac 的答案:

  1. 我设置了一个CONVFMT全局变量来处理输出格式
  2. 我还使用 bignum 参数-MPREC变量

示例片段:

#!/usr/bin/awk -M -f
BEGIN {
  FS="<|>"
  CONVFMT="%.18g"
  PREC=100
}
{
  if ($2 == "LatitudeDegrees") {
    CORR = $3 // redacted specific corrections
    print("     <LatitudeDegrees>" CORR "</LatitudeDegrees>");
  } else if ($2 == "LongitudeDegrees") {
    CORR = $3 // redacted specific corrections
    print("     <LongitudeDegrees>" CORR "</LongitudeDegrees>");
  } else {
    print($0);
  }
}
END {
}

OP 简化了他的示例,但如果 awk 脚本不是单行脚本,您不想用printfs 污染它,而是在变量中设置这样的格式。同样的精度,这样它就不会在实际的命令行调用中丢失。

相关内容