我正在寻找一种方法来告诉 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 的答案:
- 我设置了一个
CONVFMT
全局变量来处理输出格式 - 我还使用 bignum 参数
-M
和PREC
变量
示例片段:
#!/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 脚本不是单行脚本,您不想用printf
s 污染它,而是在变量中设置这样的格式。同样的精度,这样它就不会在实际的命令行调用中丢失。