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?


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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.