awk aritmatika presisi tinggi

11

Saya mencari cara untuk memberitahu awk untuk melakukan aritmatika presisi tinggi dalam operasi substitusi. Ini melibatkan, membaca bidang dari file dan menggantinya dengan kenaikan 1% pada nilai itu. Namun, saya kehilangan presisi di sana. Berikut adalah reproduksi masalah yang disederhanakan:

 $ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {print}'
   0.546748

Di sini, saya memiliki 16 digit setelah presisi desimal tetapi awk hanya memberikan enam. Menggunakan printf, saya mendapatkan hasil yang sama:

$ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}'
0.546748

Ada saran tentang cara mendapatkan presisi yang diinginkan?

mkc
sumber
Mungkin awk memiliki resolusi lebih tinggi tetapi hanya format output Anda yang terpotong. Gunakan printf.
dubiousjim
Tidak ada perubahan nilai hasil setelah menggunakan printf. Pertanyaan diedit sesuai.
mkc
Seperti @manatwork telah tunjukkan, itu gsubtidak perlu. Masalahnya adalah gsubbekerja pada string, bukan angka, jadi konversi dilakukan terlebih dahulu menggunakan CONVFMT, dan nilai default untuk itu adalah %.6g.
jw013
@ jw013, Seperti yang saya sebutkan dalam pertanyaan, masalah asli saya memerlukan gsub karena saya harus mengganti nomor dengan kenaikan 1%. Setuju, dalam contoh sederhana, itu tidak diperlukan.
mkc

Jawaban:

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

Atau lebih tepatnya di sini:

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

mungkin yang terbaik yang bisa Anda capai. Gunakan bcsebagai gantinya untuk presisi sewenang-wenang.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
Stéphane Chazelas
sumber
Jika Anda ingin presisi yang sewenang-wenang, AWKAnda dapat menggunakan -Mbendera dan mengatur PRECnilainya ke sejumlah besar
Robert Benson
3
@RobertBenson, hanya dengan GNU awk dan hanya dengan versi terbaru (4.1 atau lebih tinggi, jadi tidak pada saat jawaban itu ditulis) dan hanya ketika MPFR diaktifkan pada waktu kompilasi.
Stéphane Chazelas
2

Untuk presisi yang lebih tinggi dengan (GNU) awk (dengan bignum dikompilasi dalam) gunakan:

$ echo '0.4970436865354813' | awk -M -v PREC=100 '{printf("%.18f\n", $1)}'
0.497043686535481300

PREC = 100 berarti 100 bit, bukan 53 bit default.
Jika awk itu tidak tersedia, gunakan bc

$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943

Atau Anda perlu belajar hidup dengan ketidaktepatan mengapung yang melekat.


Di baris asli Anda ada beberapa masalah:

  • Faktor 1,1 adalah peningkatan 10%, bukan 1% (harus menjadi pengali 1,01). Saya akan menggunakan 10%.
  • Format konversi dari string ke angka (mengambang) diberikan oleh CONVFMT. Nilai standarnya adalah %.6g. Itu membatasi nilai hingga 6 digit desimal (setelah titik). Itu diterapkan pada hasil perubahan gsub dari $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
  • Format printf gmenghapus nol tambahan:

    $ 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

    Kedua masalah tersebut dapat diselesaikan dengan:

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

    Atau

    $ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}'
    0.54674805518902947 

Tetapi jangan mengerti bahwa ini berarti presisi yang lebih tinggi. Representasi nomor internal masih mengambang dalam ukuran ganda. Itu berarti 53 bit presisi dan dengan itu Anda hanya bisa memastikan 15 digit desimal yang benar, bahkan jika berkali-kali hingga 17 digit terlihat benar. Itu fatamorgana.

$ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}'
0.546748055189029469325134868996

Nilai yang benar adalah:

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943

Yang dapat juga dihitung dengan (GNU) awk jika perpustakaan bignum telah dikompilasi dalam:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
NotAnUnixNazi
sumber