对小数值求和时 awk sum 中需要完整的小数

对小数值求和时 awk sum 中需要完整的小数

我有以下 test.txt 文件 -

var,value
a,1.1234
b,1.7896749
c,2.4982
d,1.2976232

当我使用以下命令时 -

awk -F ',' '{SUM+=$2}END{print SUM}' test.txt

它打印6.7089

但保留所有小数位的结果是6.7088981 如何编写命令,以便打印结果中的所有小数位,不仅在这种特定情况下,而且在一般情况下。例如,如果结果有10位小数,那么它应该打印所有10位小数吗?如果结果只有 5 位小数,则应该只打印 5 位小数。我使用的操作系统是 Red Hat Enterprise Linux Server 7.7

答案1

OFMT打印时,使用包含printf格式规范的特殊变量(默认情况下)将非整数转换为十进制字符串表示形式%.6g。您可以将其更改为%.17g以获得 IEEE 754 双精度二进制浮点数的最大精度(awk大多数系统上的大多数实现在内部使用)。另一个变量 ( CONVFMT) 用于浮点数隐式转换为字符串的其他情况(例如将数字与其他内容连接时)

使用这些双精度数不会获得更高的精度,超过 17 没有任何意义。已经使用 17,您可能会看到一些伪影。如果您不需要那么高的精度,15 位有效数字可能会更好。

$ awk -v OFMT=%.17g -F ',' '{SUM+=$2};END{print SUM}' < file
6.7088981000000008
$ awk -v OFMT=%.15g -F ',' '{SUM+=$2};END{print SUM}' < file
6.7088981

虽然OFMT会影响所有打印的浮点数,但您也可以printf直接使用以所需的精度打印数字。

$ awk  -F ',' '{SUM+=$2};END{printf "%.15g\n", SUM}' < file
6.7088981

自版本 4.1.0 起, GNU 实现awk也可以使用任意精度算术支持进行编译(请参阅 参考资料info gawk 'Arbitrary Precision Arithmetic')。如果您的系统出现这种情况,您还可以执行以下操作:

gawk -M -v PREC=256 -v OFMT=%.60g -F ',' '{SUM+=$2};END{print SUM}' < file

例子:

$ printf 'x,%s\n' 1 1000000000000000000000000000000000.00000000001 |
> gawk -v OFMT=%.15g -F ',' '{SUM+=$2};END{print SUM}'
999999999999999945575230987042816
$ printf 'x,%s\n' 1 1000000000000000000000000000000000.00000000001 |
> gawk -M -v PREC=256 -v OFMT=%.60g -F ',' '{SUM+=$2};END{print SUM}'
1000000000000000000000000000000001.00000000001

这里的另一种方法可以是使用bc(假设这些数字总是这样表达(0.001,不是1e-3例如)):

<file tail -n+2 | # skip header
  cut -d, -f2   | # extract second field
  paste -sd + - | # join input lines with +
  bc

后面的位数.将是任何输入记录中的最大位数。

答案2

GNU 数据混合使用默认输出设置以所需精度显示数字:

$ datamash --header-in -t, sum 2 < test.txt
6.7088981

OFMT或者使用更精确的不同 awk :

$ awk -F, -v OFMT='%.10g' '{sum += $2} END { print sum }' test.txt
6.7088981

但看浮点数学有问题吗?。以 10 为基数显示浮点数时,小数点后的位数并不总是与(大多数)计算机使用的 IEEE754、以 2 为基数的表示形式相对应。

答案3

正如已经讨论过的,当试图获得直观的答案时,浮点运算是一个问题,但如果您知道您的输入在“.”之前最多只能有 3 位数字。比如说 9 之后,您可以使用字符串操作将数字转换为小数,然后将它们相加以避免浮点算术问题,然后在打印之前将结果再次转换回 FP,例如:

$ cat tst.awk
BEGIN {
    FS = ","
    bef = 3
    aft = 9
}
NR>1 {
    split($2,f,".")
    val = sprintf("%*s%-*s",bef,f[1],aft,f[2])
    gsub(/ /,0,val)
    sum += val
}
END {
    sub(".{"aft"}$",".&",sum)
    sub(/0+$/,"",sum)
    print sum
}

$ awk -f tst.awk file
6.7088981

如果 3 和/或 9 对您来说不够大,请选择其他数字或执行 2 遍方法,在第一遍中计算出每个数字的最大值,例如:

$ cat tst.awk
BEGIN { FS = "," }
FNR==1 { next }
{ split($2,f,".") }
NR==FNR {
    bef = (length(f[1]) > bef ? length(f[1]) : bef)
    aft = (length(f[2]) > aft ? length(f[2]) : aft)
    next
}
{
    val = sprintf("%*s%-*s",bef,f[1],aft,f[2])
    gsub(/ /,0,val)
    sum += val
}
END {
    sub(".{"aft"}$",".&",sum)
    sub(/0+$/,"",sum)
    print sum
}

$ awk -f tst.awk file file
6.7088981

相关内容