Estoy buscando una manera de decirle a awk que haga aritmética de alta precisión en una operación de sustitución. Esto implica leer un campo de un archivo y sustituirlo con un incremento del 1% sobre ese valor. Sin embargo, estoy perdiendo precisión ahí. Aquí hay una reproducción simplificada del problema:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Aquí, tengo 16 dígitos después de la precisión decimal pero awk da solo seis. Usando printf, obtengo el mismo resultado:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
¿Alguna sugerencia sobre cómo obtener la precisión deseada?
Comentarios
Responder
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
O mejor dicho aquí:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
es probablemente lo mejor que puede lograr. Utilice bc
en su lugar para obtener una precisión arbitraria.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Comentarios
- Si desea precisión arbitraria en
AWK
, puede usar la marca-M
y establecer laPREC
valor a un número grande - @RobertBenson, solo con GNU awk y solo con versiones recientes (4.1 o superior, por lo que no en el momento en que se escribió la respuesta) y solo cuando MPFR estaba habilitado en la compilación tiempo.
Respuesta
Para mayor precisión con (GNU) awk (con bignum compilado en) use:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
El PREC = 100 significa 100 bits en lugar de los 53 bits predeterminados.
Si ese awk no está disponible, use bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
O tendrá que aprender a vivir con la imprecisión inherente de los flotadores.
En sus líneas originales hay varios problemas:
- Un factor de 1,1 es un aumento del 10%, no del 1% (debe ser un Multiplicador 1,01). Usaré el 10%.
-
El formato de conversión de una cadena a un número (flotante) lo proporciona CONVFMT. Su valor predeterminado es
%.6g
. Eso limita los valores a 6 dígitos decimales (después del punto). Eso se aplica al resultado del cambio 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
-
El formato printf
g
elimina los ceros finales:$ 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 problemas podrían resolverse con:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
O
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Pero no se haga la idea de que esto significa mayor precisión. La representación del número interno sigue siendo un flotante en tamaño doble. Eso significa 53 bits de precisión y con eso solo puede estar seguro de 15 dígitos decimales correctos, incluso si muchas veces hasta 17 dígitos parecen correctos. Eso es «un espejismo.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
El valor correcto es:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Que podría también se calculará con (GNU) awk si la biblioteca bignum se ha compilado en:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Respuesta
Mi script awk es más grande que una sola línea, así que usé la combinación de las respuestas de Stéphane Chazelas e Isaac:
- Configuré el
CONVFMT
variable que se ocupará globalmente del formato de salida - También utilizo el parámetro bignum
-M
junto con elPREC
variable
Fragmento de ejemplo:
#!/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 simplificó su ejemplo, pero si el El script awk no es un trazador de líneas con el que no quiere contaminarlo con printf
s, pero establezca el formato de esta manera en la variable. Asimismo, la precisión para que no se pierda en la invocación real de la línea de comandos.
gsub
no es necesario. El problema es quegsub
funciona con cadenas, no con números, por lo que primero se realiza una conversión usandoCONVFMT
, y el valor predeterminado para eso es%.6g
.