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
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 lePREC
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:
- Jai défini le
CONVFMT
variable qui soccupera globalement du formatage de la sortie - Jutilise aussi le paramètre bignum
-M
avec lePREC
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.
gsub
nest pas nécessaire. Le problème est quegsub
fonctionne sur des chaînes, pas sur des nombres, donc une conversion est dabord effectuée en utilisantCONVFMT
, et la valeur par défaut est%.6g
.