Estou procurando uma maneira de dizer ao awk para fazer aritmética de alta precisão em uma operação de substituição. Isso envolve a leitura de um campo de um arquivo e a sua substituição por um incremento de 1% nesse valor. No entanto, estou perdendo precisão aí. Aqui está uma reprodução simplificada do problema:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Aqui, eu tenho 16 dígitos após a precisão decimal, mas awk dá apenas seis. Usando printf, estou obtendo o mesmo resultado:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
Alguma sugestão sobre como obter a precisão desejada?
Comentários
Resposta
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
Ou melhor aqui:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
é provavelmente o melhor que você pode conseguir. Use bc
em vez de precisão arbitrária.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Comentários
- Se quiser precisão arbitrária em
AWK
, você pode usar o sinalizador-M
e definir oPREC
valor para um grande número - @RobertBenson, apenas com GNU awk e apenas com versões recentes (4.1 ou superior, portanto não no momento em que a resposta foi escrita) e somente quando MPFR foi habilitado na compilação entretanto.
Resposta
Para maior precisão com (GNU) awk (com bignum compilado em), use:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
O PREC = 100 significa 100 bits em vez dos 53 bits padrão.
Se esse awk não estiver disponível, use bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
Ou você precisará aprender a conviver com a imprecisão inerente dos flutuadores.
Em suas linhas originais, há vários problemas:
- Um fator de 1,1 é 10% de aumento, não 1% (deveria ser um Multiplicador 1,01). Usarei 10%.
-
O formato de conversão de uma string para um número (flutuante) é fornecido por CONVFMT. Seu valor padrão é
%.6g
. Isso limita os valores a 6 dígitos decimais (após o ponto). Isso é aplicado ao resultado da alteração gsub de$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
-
O formato printf
g
remove os zeros finais:$ 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
Ambos os problemas podem ser resolvidos com:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
Ou
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Mas não pense que isso significa maior precisão. A representação do número interno ainda é um float em tamanho duplo. Isso significa 53 bits de precisão e com isso você só poderia ter certeza de 15 dígitos decimais corretos, mesmo se muitas vezes até 17 dígitos parecerem corretos. Isso “é uma miragem.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
O valor correto é:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Que poderia também pode ser calculado com (GNU) awk se a biblioteca bignum foi compilada em:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Resposta
Meu script awk é maior do que apenas uma linha, então usei a combinação das respostas de Stéphane Chazelas e Isaac:
- Eu defini
CONVFMT
que cuidará globalmente da formatação de saída - Eu também uso o parâmetro bignum
-M
junto comPREC
variável
Trecho de exemplo:
#!/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 simplificou seu exemplo, mas se o O script awk não é um liner que você não deseja poluir com printf
s, mas defina o formato como este na variável. Da mesma forma, a precisão para que não se perca na chamada real da linha de comando.
gsub
é desnecessário. O problema é quegsub
funciona em strings, não em números, então uma conversão é feita primeiro usandoCONVFMT
, e o valor padrão para isso é%.6g
.