awk เลขคณิตความแม่นยำสูง


11

ฉันกำลังมองหาวิธีที่จะบอก awk ให้ทำเลขคณิตความแม่นยำสูงในการดำเนินการทดแทน สิ่งนี้เกี่ยวข้องกับการอ่านฟิลด์จากไฟล์และแทนที่ด้วยการเพิ่มค่า 1% อย่างไรก็ตามฉันสูญเสียความแม่นยำที่นั่น นี่คือการทำสำเนาของปัญหาที่ง่ายขึ้น:

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

ที่นี่ฉันมี 16 หลักหลังจากความแม่นยำทศนิยม แต่ awk ให้เพียงหก เมื่อใช้ printf ฉันจะได้ผลลัพธ์เดียวกัน:

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

ข้อเสนอแนะเกี่ยวกับวิธีการได้รับความแม่นยำที่ต้องการ?


บางที awk มีความละเอียดสูงกว่า แต่เป็นเพียงการจัดรูปแบบผลลัพธ์ของคุณตัดทอน ใช้ printf
dubiousjim

ไม่มีการเปลี่ยนแปลงค่าผลลัพธ์หลังจากใช้ printf แก้ไขคำถามแล้ว
mkc

@manatwork ได้ชี้ให้เห็นแล้วว่าgsubไม่จำเป็น ปัญหาคือgsubการทำงานในสายที่ไม่ได้หมายเลขดังนั้นการแปลงจะทำครั้งแรกที่ใช้และความคุ้มค่าเริ่มต้นสำหรับการที่เป็นCONVFMT %.6g
jw013

@ jw013 ตามที่ฉันกล่าวถึงในคำถามปัญหาดั้งเดิมของฉันต้องใช้ gsub เนื่องจากฉันต้องการแทนที่ตัวเลขด้วยการเพิ่มขึ้น 1% เห็นด้วยในตัวอย่างง่าย ๆ มันไม่จำเป็น
mkc

คำตอบ:


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

หรือค่อนข้างที่นี่:

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

น่าจะเป็นสิ่งที่ดีที่สุดที่คุณสามารถทำได้ ใช้bcแทนเพื่อความแม่นยำโดยพลการ

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

หากคุณต้องการความแม่นยำโดยพลการในAWKคุณสามารถใช้การ-Mตั้งค่าสถานะและตั้งPRECค่าเป็นจำนวนมาก
Robert Benson

3
@RobertBenson เฉพาะกับ GNU awk และเฉพาะกับรุ่นล่าสุด (4.1 ขึ้นไปดังนั้นไม่ใช่ในเวลาที่เขียนคำตอบ) และเมื่อเปิดใช้งาน MPFR ในเวลารวบรวมเท่านั้น
Stéphane Chazelas

2

เพื่อความแม่นยำที่สูงขึ้นด้วย (GNU) awk (ด้วยการรวบรวม bignum ใน):

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

PREC = 100 หมายถึง 100 บิตแทนค่าเริ่มต้น 53 บิต
หาก awk นั้นไม่พร้อมใช้งานให้ใช้ bc

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

หรือคุณจะต้องเรียนรู้ที่จะอยู่กับความไม่แน่นอนของการลอยตัว


ในบรรทัดเดิมของคุณมีปัญหาหลายประการ:

  • ปัจจัย 1.1 คือเพิ่มขึ้น 10% ไม่ใช่ 1% (ควรเป็นตัวคูณ 1.01) ฉันจะใช้ 10%
  • รูปแบบการแปลงจากสตริงไปยังหมายเลข (ลอย) ถูกกำหนดโดย CONVFMT %.6gค่าเริ่มต้นของมันคือ ที่ จำกัด ค่าไว้ที่ 6 หลักทศนิยม (หลังจุด) ที่ถูกนำไปใช้กับผลของการเปลี่ยนแปลง gsub $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
    
  • รูปแบบ printf gลบศูนย์ต่อท้าย:

    $ 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
    

    ปัญหาทั้งสองสามารถแก้ไขได้ด้วย:

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

    หรือ

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

แต่อย่าเข้าใจว่านี่หมายถึงความแม่นยำสูงกว่า การแสดงตัวเลขภายในยังคงเป็นแบบทศนิยมสองเท่า นั่นหมายถึงความแม่นยำ 53 บิตโดยที่คุณสามารถมั่นใจได้ว่ามีทศนิยม 15 หลักที่ถูกต้องแม้ว่าจำนวนครั้งมากถึง 17 หลักจะดูถูกต้อง นั่นเป็นภาพลวงตา

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

ค่าที่ถูกต้องคือ:

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

ซึ่งสามารถคำนวณได้ด้วย (GNU) awk หากมีการรวบรวมไลบรารี bignum ใน:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.