awk高精度演算


11

私はawkに高精度の算術演算を代入演算で行うように指示する方法を探しています。これには、ファイルからフィールドを読み取り、その値を1%の増分で置き換えることが含まれます。しかし、私はそこで精度を失っています。これは問題の簡単な再現です:

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

ここでは、小数点以下16桁の精度がありますが、awkでは6桁しかありません。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、質問で述べたように、1%の増分で数値を置き換える必要があるため、元の問題ではgsubが必要です。簡略化された例では、これは必須ではないことに同意します。
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がコンパイル時に有効にされた場合のみ。
ステファンChazelas

2

(GNU)awk(bignumがコンパイルされている)で精度を上げるには、次のようにします。

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

PREC = 100は、デフォルトの53ビットではなく100ビットを意味します。
そのawkが利用できない場合は、bcを使用します。

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

または、フロートの本質的な不正確さとともに生きることを学ぶ必要があります。


元の行にはいくつかの問題があります:

  • 1.1の係数は、1%ではなく10%の増加です(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ビットであり、17桁まで正確に見える場合でも、正しい10進数は15桁しかありません。それはミラージュです。

$ 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

これは、bignumライブラリがコンパイルされている場合、(GNU)awkでも計算できます。

$ 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.