awk aritmética de alta precisión

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

  • Quizás awk tenga una resolución más alta, pero ' s solo el formato de salida se está truncando. Use printf.
  • No hay cambios en el valor del resultado después de usar printf. Pregunta editada en consecuencia.
  • Como ha señalado @manatwork, gsub no es necesario. El problema es que gsub funciona con cadenas, no con números, por lo que primero se realiza una conversión usando CONVFMT, y el valor predeterminado para eso es %.6g.
  • @ jw013, Como mencioné en la pregunta, mi problema original requiere gsub ya que necesito sustituir un número con un incremento del 1%. De acuerdo, en el ejemplo simplificado, no es necesario.

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 la PREC 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:

  1. Configuré el CONVFMT variable que se ocupará globalmente del formato de salida
  2. También utilizo el parámetro bignum -M junto con el PREC 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.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *