awk số học chính xác cao


11

Tôi đang tìm cách để nói với awk thực hiện số học có độ chính xác cao trong một phép toán thay thế. Điều này bao gồm, đọc một trường từ một tệp và thay thế nó với mức tăng 1% trên giá trị đó. Tuy nhiên, tôi đang mất độ chính xác ở đó. Đây là một bản tái tạo đơn giản của vấn đề:

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

Ở đây, tôi có 16 chữ số sau độ chính xác thập phân nhưng awk chỉ cung cấp sáu chữ số. Sử dụng printf, tôi nhận được kết quả tương tự:

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

Bất kỳ đề xuất về làm thế nào để có được độ chính xác mong muốn?


Có lẽ awk có độ phân giải cao hơn nhưng chỉ là định dạng đầu ra của bạn bị cắt ngắn. Sử dụng printf.
dubiousjim

Không có thay đổi về giá trị kết quả sau khi sử dụng printf. Câu hỏi được chỉnh sửa cho phù hợp.
mkc

Như @manatwork đã chỉ ra, điều đó gsublà không cần thiết. Vấn đề là gsubhoạt động trên các chuỗi, không phải số, vì vậy việc chuyển đổi được thực hiện trước tiên bằng cách sử dụng CONVFMTvà giá trị mặc định cho điều đó là %.6g.
jw013

@ jw013, Như tôi đã đề cập trong câu hỏi, vấn đề ban đầu của tôi yêu cầu gsub vì tôi cần thay thế một số với mức tăng 1%. Đồng ý, trong ví dụ đơn giản hóa, nó không bắt buộc.
mkc

Câu trả lời:


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

Hay đúng hơn là ở đây:

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

có lẽ là tốt nhất bạn có thể đạt được. Sử dụng bcthay thế cho độ chính xác tùy ý.

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

Nếu bạn muốn độ chính xác tùy ý, AWKbạn có thể sử dụng -Mcờ và đặt PRECgiá trị thành một số lớn
Robert Benson

3
@RobertBenson, chỉ với GNU awk và chỉ với các phiên bản gần đây (4.1 trở lên, vì vậy không phải lúc đó câu trả lời được viết) và chỉ khi MPFR được bật vào thời gian biên dịch.
Stéphane Chazelas

2

Để có độ chính xác cao hơn với (GNU) awk (với bignum được biên dịch), hãy sử dụng:

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

PREC = 100 có nghĩa là 100 bit thay vì 53 bit mặc định.
Nếu awk đó không có sẵn, sử dụng bc

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

Hoặc bạn sẽ cần phải học cách sống với sự thiếu quyết đoán vốn có của phao.


Trong các dòng ban đầu của bạn có một số vấn đề:

  • Hệ số 1,1 là tăng 10%, không phải 1% (nên là số nhân 1,01). Tôi sẽ sử dụng 10%.
  • Định dạng chuyển đổi từ một chuỗi thành một số (nổi) được đưa ra bởi CONVFMT. Giá trị mặc định của nó là %.6g. Điều đó giới hạn các giá trị ở 6 chữ số thập phân (sau dấu chấm). Điều đó được áp dụng cho kết quả của sự thay đổi gsub của $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
  • Định dạng printf gloại bỏ các số 0 ở cuối:

    $ 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

    Cả hai vấn đề có thể được giải quyết với:

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

    Hoặc là

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

Nhưng đừng hiểu rằng điều này có nghĩa là độ chính xác cao hơn. Các đại diện số nội bộ vẫn là một float trong kích thước gấp đôi. Điều đó có nghĩa là độ chính xác 53 bit và với điều đó bạn chỉ có thể chắc chắn 15 chữ số thập phân chính xác, ngay cả khi nhiều lần lên đến 17 chữ số trông chính xác. Đó là một ảo ảnh.

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

Giá trị đúng là:

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

Điều này cũng có thể được tính bằng awk (GNU) nếu thư viện bignum đã được biên dịch trong:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.