awk arithmétique de haute précision

Je cherche un moyen de dire à awk de faire de larithmétique de haute précision dans une opération de substitution. Cela implique de lire un champ à partir dun fichier et de le remplacer par un incrément de 1% sur cette valeur. Cependant, jy perds de la précision. Voici une reproduction simplifiée du problème:

 $ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748 

Ici, jai 16 chiffres après la précision décimale mais awk nen donne que six. En utilisant printf, jobtiens le même résultat:

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

Des suggestions sur la façon dobtenir la précision souhaitée?

Commentaires

  • Peut-être awk a une résolution plus élevée, mais il ' s que votre formatage de sortie est tronqué. Utilisez printf.
  • Aucune modification de la valeur du résultat après lutilisation de printf. Question modifiée en conséquence.
  • Comme @manatwork la souligné, gsub nest pas nécessaire. Le problème est que gsub fonctionne sur des chaînes, pas sur des nombres, donc une conversion est dabord effectuée en utilisant CONVFMT, et la valeur par défaut est %.6g.
  • @ jw013, Comme je lai mentionné dans la question, mon problème dorigine nécessite gsub car je dois remplacer un nombre avec un incrément de 1%. Daccord, dans lexemple simplifié, ce nest pas obligatoire.

Réponse

$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947 

Ou plutôt ici:

$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947 

est probablement le meilleur que vous puissiez réaliser. Utilisez plutôt bc pour une précision arbitraire.

$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943 

Commentaires

  • Si vous voulez une précision arbitraire dans AWK, vous pouvez utiliser lindicateur -M et définir le PREC valeur à un grand nombre
  • @RobertBenson, uniquement avec GNU awk et uniquement avec les versions récentes (4.1 ou supérieur, donc pas au moment où la réponse a été écrite) et uniquement lorsque MPFR était activé lors de la compilation temps.

Réponse

Pour une plus grande précision avec (GNU) awk (avec bignum compilé dans), utilisez:

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

Le PREC = 100 signifie 100 bits au lieu des 53 bits par défaut.
Si cet awk nest pas disponible, utilisez bc

$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943 

Ou vous devrez apprendre à vivre avec limprécision inhérente aux flotteurs.


Dans vos lignes dorigine, il y a plusieurs problèmes:

  • Un facteur de 1,1 est une augmentation de 10% et non de 1% (devrait être 1,01 multiplicateur). Jutiliserai 10%.
  • Le format de conversion dune chaîne en un nombre (flottant) est donné par CONVFMT. Sa valeur par défaut est %.6g . Cela limite les valeurs à 6 chiffres décimaux (après le point). Cela est appliqué au résultat de la modification 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 
  • Le format printf g supprime les zéros de fin:

    $ 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 

    Les deux problèmes peuvent être résolus avec:

    $ 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 

Mais ne pensez pas que cela signifie une plus grande précision. La représentation numérique interne est toujours un flottant de taille double. Cela signifie 53 bits de précision et avec cela, vous ne pouvez être sûr que de 15 chiffres décimaux corrects, même si plusieurs fois jusquà 17 chiffres semblent corrects. Cest « un mirage.

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

La valeur correcte est:

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943 

Ce qui pourrait être également calculé avec (GNU) awk si la bibliothèque bignum a été compilée en:

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

Réponse

Mon script awk est plus gros quune simple ligne, jai donc utilisé la combinaison des réponses de Stéphane Chazelas et Isaac:

  1. Jai défini le CONVFMT variable qui soccupera globalement du formatage de la sortie
  2. Jutilise aussi le paramètre bignum -M avec le PREC variable

Exemple dextrait:

#!/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 a simplifié son exemple, mais si le awk script nest pas une seule ligne que vous ne voulez pas polluer avec des printf, mais définissez le format comme celui-ci dans la variable. De même, la précision pour ne pas se perdre dans lappel de la ligne de commande.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *