置換演算で高精度演算を実行するように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
必要な精度を得る方法について何か提案はありますか?
コメント
回答
$ 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の回答を組み合わせて使用しました。
-
CONVFMT
変数は出力フォーマットをグローバルに処理します - また、bignumパラメーター
-M
をPREC
変数
スニペットの例:
#!/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
で汚染したくない単一のライナーではありませんが、変数にこのような形式を設定します。同様に、実際のコマンドライン呼び出しで失われないようにするための精度。
gsub
は不要です。問題は、gsub
が数値ではなく文字列で機能するため、最初にCONVFMT
を使用して変換が行われ、そのデフォルト値は%.6g
。