awk高精度演算

置換演算で高精度演算を実行するように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を使用します。
  • printfを使用した後も結果値に変化はありません。質問はそれに応じて編集されました。
  • @manatworkが指摘しているように、gsubは不要です。問題は、gsubが数値ではなく文字列で機能するため、最初にCONVFMTを使用して変換が行われ、そのデフォルト値は%.6g
  • @ jw013、質問で述べたように、1%の増分で数値を置き換える必要があるため、元の問題ではgsubが必要です。同意しました。簡略化した例では、必須ではありません。

回答

$ 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値を多数に
  • @ RobertBenson、GNU awkのみ、最新バージョン(4.1以降、回答が書かれた時点ではない)、およびコンパイル時にMPFRが有効になっている場合のみただし、時間です。

回答

(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桁(ドットの後)に制限されます。これは、$1のgsub変更の結果に適用されます。

    $ 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 

しかし、これがより高い精度を意味するという考えを理解しないでください。内部数値表現は、依然として2倍のサイズの浮動小数点数です。つまり、53ビットの精度であり、最大17桁が正しく見える場合でも、15桁の正しい10進数しか確認できませんでした。それは「サミラージュ」です。

$ 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 

回答

私のawkスクリプトは1つのライナーよりも大きいので、StéphaneChazelasとIsaacの回答を組み合わせて使用しました。

  1. CONVFMT変数は出力フォーマットをグローバルに処理します
  2. また、bignumパラメーター-MPREC変数

スニペットの例:

#!/usr/bin/awk -M -f BEGIN { FS="<|>" CONVFMT="%.18g" PREC=100 } { if ($2 == "LatitudeDegrees") { CORR = $3 // redacted specific corrections print(" <LatitudeDegrees>" CORR "</LatitudeDegrees>"); } else if ($2 == "LongitudeDegrees") { CORR = $3 // redacted specific corrections print(" <LongitudeDegrees>" CORR "</LongitudeDegrees>"); } else { print($0); } } END { } 

OPは彼の例を簡略化しましたが、 awkスクリプトは、printfで汚染したくない単一のライナーではありませんが、変数にこのような形式を設定します。同様に、実際のコマンドライン呼び出しで失われないようにするための精度。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です