awk 高精度算术

mkc*_*mkc 19 awk arithmetic floating-point

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

 $ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {print}'
   0.546748
Run Code Online (Sandbox Code Playgroud)

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

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

关于如何获得所需精度的任何建议?

Sté*_*las 18

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

或者更确切地说,在这里:

$ echo 0.4970436865354813 | awk '{printf "%.17g\n", $1*1.1}'
0.54674805518902947
Run Code Online (Sandbox Code Playgroud)

可能是你能做到的最好的。使用bc而不是为任意精度。

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
Run Code Online (Sandbox Code Playgroud)

  • @RobertBenson,仅适用于 GNU awk 且仅适用于最新版本(4.1 或更高版本,因此不是在编写答案时),并且仅适用于在编译时启用 MPFR 的情况。 (3认同)

小智 6

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

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

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

$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943
Run Code Online (Sandbox Code Playgroud)

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


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

  • 系数 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
    
    Run Code Online (Sandbox Code Playgroud)
  • 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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    或者

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

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

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

正确的值为:

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943
Run Code Online (Sandbox Code Playgroud)

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

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