0.02インクリメントのbashループ


11

私が試した増分として0.02のbashでforループを作成したい

for ((i=4.00;i<5.42;i+=0.02))
do
commands
done

しかし、それはうまくいきませんでした。


9
Bashは浮動小数点演算を行いません。
ヨルダン2015

1
増分はによって行うことができますがbc、4.52で停止するのは難しい場合があります。@roaima提案を使用して、2のステップと補助VARを有し、使用i=$(echo $tmp_var / 100 | bc)
Archemar


5
通常、フロートをループインデックスとして使用する必要ありません。各反復でエラーを累積しています。
isanae 2015

回答:


18

bash manページを読むと、次の情報が得られます。

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

まず、算術式expr1は、以下の「算術評価」で説明する規則に従って評価されます。[...]

そして、このセクションを取得します

算術評価

シェルを使用すると、特定の状況下で算術式を評価できます(letおよびdeclare組み込みコマンドと算術展開を参照)。評価はオーバーフローのチェックなしで固定幅整数で行われます[...]

したがって、for整数以外の値でループを使用できないことがはっきりとわかります。

1つの解決策は、単純にすべてのループコンポーネントを100倍して、後で使用する場合に次のようにすることです。

for ((k=400;k<542;k+=2))
do
    i=$(bc <<<"scale=2; $k / 100" )    # when k=402 you get i=4.02, etc.
    ...
done

これは、k=400;k<542;k+=2潜在的な浮動小数点演算の問題も回避できる最善の解決策だと思います。
ホイヘンス

1
ループの反復ごとに、パイプを作成し(の出力を読み取るためbc)、プロセスをフォークし、一時ファイル(here-string用)を作成し、その中で実行bcします(つまり、実行可能ファイルと共有ライブラリをロードし、それらを初期化する)、それを待ってクリーンアップします。bcループを実行するために1回実行すると、はるかに効率的になります。
ステファンChazelas

@StéphaneChazelasはい、同意しました。しかし、これがボトルネックである場合は、おそらくコードを間違った言語で記述していると思われます。効率の悪いOOI(!)?i=$(bc <<< "scale...")またはi=$(echo "scale..." | bc)
roaima

1
私の簡単なテストから、パイプバージョンはzsh(どこ<<<から来たのか)bashおよびで高速kshです。bashとにかく、他のシェルに切り替えると、他の構文を使用するよりもパフォーマンスが向上することに注意してください。
ステファンChazelas

(サポートすることをシェルの最も<<<(zshの、mksh、は、ksh93、ヤシュ)も(浮動小数点演算をサポートしzshksh93yash))。
ステファンChazelas

18

シェルでのループを避けます。

算術演算を行う場合は、awkまたはを使用しますbc

awk '
  BEGIN{
    for (i = 4.00; i < 5.42; i+ = 0.02)
      print i
  }'

または

bc << EOF
for (i = 4.00; i < 5.42; i += 0.02)  i
EOF

awk(とは対照的にbc)はプロセッサのdouble浮動小数点数表現(IEEE 754タイプなど)で機能することに注意してください。結果として、これらの数値はこれらの10進数の2進近似であるため、いくつかの驚きがあるかもしれません。

$ gawk 'BEGIN{for (i=0; i<=0.3; i+=0.1) print i}'
0
0.1
0.2

を追加するOFMT="%.17g"と、欠落の理由を確認できます0.3

$ gawk 'BEGIN{OFMT="%.17g"; for (i=0; i<=0.5; i+=0.1) print i}'
0
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.5

bc 任意精度なので、この種の問題はありません。

デフォルトでは(出力形式を明示的な形式指定でOFMT使用するかprintf、明示的な形式指定で使用しない限り)は、浮動小数点数の表示にawk使用さ%.6gれるため、1,000,000を超える浮動小数点数の場合は1e6以上に切り替え、大きい数(100000.02)の小数部分を切り捨てます100000と表示されます)。

あなたは本当に、たとえばあなたがそのループの反復ごとに特定のコマンドを実行したいので、シェルのループを使用し、いずれかのように小数点演算のサポートを浮動してシェルを使用する必要がない場合zshyashまたはksh93上記のように1つのコマンドで値のリストをまたは生成します(またはseq利用可能な場合)、その出力をループします。

お気に入り:

unset -v IFS # configure split+glob for default word splitting
for i in $(seq 4 0.02 5.42); do
  something with "$i"
done

または:

seq 4 0.02 5.42 | while IFS= read i; do
  something with "$i"
done

プロセッサの浮動小数点数の制限を超えない限り、seqは、浮動小数点近似によって発生するエラーを、awk上記のバージョンよりも適切に処理します。

seq(GNUコマンド)がない場合は、次のような関数としてより信頼性の高いものを作成できます。

seq() { # args: first increment last
  bc << EOF
    for (i = $1; i <= $3; i += $2) i
EOF
}

それはのようにうまくいくでしょうseq 100000000001 0.000000001 100000000001.000000005。ただし、サポートされていないコマンドに渡す場合は、任意の高精度の数値を使用してもあまり効果がないことに注意してください。


awkの使用に感謝します!+1
Pandya

unset IFS最初の例ではなぜ必要なのですか?
user1717828

@ user1717828、理想的には、split + globの呼び出しで、改行文字で分割する必要があります。私たちはそれを行うことができますがIFS=$'\n'、すべてのシェルで動作するわけではありません。またはIFS='<a-litteral-newline-here>'、それはあまり読みにくいです。または、代わりに単語(スペース、タブ、改行)で分割することもできます。これは、$ IFSのデフォルト値で取得する場合や、IFSを設定解除してここでも機能する場合などです。
ステファンChazelas

@ user1717828:で混乱させる必要はありません。これはIFSseq'出力に分割を回避するために必要なスペースがないことがわかっているためです。この例は、この例がに依存していることを確認するためにほとんどありますIFS。これは、別のリスト生成コマンドで問題になる可能性があります。
Peter Cordes

1
@PeterCordes、そこにあるので、IFSが事前に何に設定されているかを想定する必要はありません。
ステファンChazelas


0

他の人が示唆しているように、あなたはbcを使うことができます:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.