awk aritmética de alta precisão

9

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 ler um campo de um arquivo e substituí-lo por um incremento de 1% nesse valor. No entanto, estou perdendo a precisão lá. 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 o awk dá apenas seis. Usando o 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?

    
por Ketan 28.11.2012 / 16:19

2 respostas

11
$ 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 para precisão arbitrária.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
    
por 28.11.2012 / 16:41
1

Para maior precisão com o uso do (GNU) awk (com o bignum compilado):

$ 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 viver com a imprecisão inerente dos carros alegóricos.

Nas suas linhas originais, existem vários problemas:

  • Um fator de 1,1 é 10% de aumento, não 1% (deve ser um multiplicador de 1,01). Vou usar 10%.
  • O formato de conversão de uma string para um número (flutuante) é dado pelo CONVFMT. Seu valor padrão é %.6g . Isso limita os valores a 6 dígitos decimais (após o ponto). Isso é aplicado ao resultado da mudança de 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 à direita:

    $ 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 interna do número ainda é um float em tamanho duplo. Isso significa 53 bits de precisão e, com isso, você só pode ter certeza de 15 dígitos decimais corretos, mesmo que muitas vezes até 17 dígitos pareçam 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

O qual 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
    
por 21.08.2018 / 00:07