awk aritmética de alta precisión

11

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% en ese valor. Sin embargo, estoy perdiendo precisión allí. 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?

mkc
fuente
Quizás awk tenga una resolución más alta, pero es solo que el formato de salida se está truncando. Utiliza printf.
dubiousjim
No hay cambios en el valor del resultado después de usar printf. Pregunta editada en consecuencia.
mkc
Como ha señalado @manatwork, eso gsubes innecesario. El problema es que gsubfunciona en cadenas, no en números, por lo que primero se realiza una conversión CONVFMT, y el valor predeterminado para eso es %.6g.
jw013
@ 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 obligatorio.
mkc

Respuestas:

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

O más bien aquí:

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

es probablemente lo mejor que puedes lograr. Use en su bclugar para una precisión arbitraria.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
Stéphane Chazelas
fuente
Si desea una precisión arbitraria AWK, puede usar la -Mbandera y establecer el PRECvalor en un gran número
Robert Benson
3
@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 el momento de la compilación.
Stéphane Chazelas
2

Para mayor precisión con (GNU) awk (con bignum compilado) 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 necesitará 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% (debería ser un multiplicador de 1.01). Usaré el 10%.
  • CONVFMT proporciona el formato de conversión de una cadena a un número (flotante). 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 gelimina 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 tenga la idea de que esto significa mayor precisión. La representación interna del número sigue siendo 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 también podría calcularse 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
NotAnUnixNazi
fuente