awk高精度演算

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

必要な精度を得る方法に関する提案はありますか?

答え1

$ 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

答え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 
    

しかし、これがより高い精度を意味するとは思わないでください。内部数値表現は依然として2倍のサイズの浮動小数点数です。これは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

答え3

私のawkスクリプトは1行以上のコードなので、Stéphane Chazelasと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スクリプトが1行スクリプトではない場合、sで汚染したくないので、変数でprintfこのように書式設定します。精度は同じなので、実際のコマンドライン呼び出しで失われることはありません。

関連情報