0.02 artışlı bash loop


11

Denediğim artışlarla 0,02 ile bash'da bir for döngüsü yapmak istiyorum

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

ama işe yaramadı.


9
Bash kayan nokta matematiği yapmaz.
jordanm

1
artış yapılabilir bc, ancak 4.52 üzerinde durmak zor olabilir. @roaima önerisini kullanın, 2. adımda yardımcı varya sahip olun ve kullanıni=$(echo $tmp_var / 100 | bc)
Archemar


5
Normalde şamandıraları döngü dizini olarak kullanmak istemezsiniz . Her yinelemede hata biriktiriyorsunuz.
isanae

Yanıtlar:


18

Kılavuz bash sayfasını okumak aşağıdaki bilgileri verir:

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

İlk olarak, aritmetik ifade expr1aşağıda ARİTMETİK DEĞERLENDİRME altında açıklanan kurallara göre değerlendirilir. [...]

ve sonra bu bölümü alıyoruz

ARİTMETİK DEĞERLENDİRME

Kabuk, belirli koşullar altında aritmetik ifadelerin değerlendirilmesine izin verir (bkz. letVe declareyerleşik komutları ve Aritmetik Genişleme). Değerlendirme taşma kontrolü olmadan sabit genişlikli tamsayılarda yapılır [...]

Dolayısıyla, fortamsayı olmayan değerlere sahip bir döngü kullanamayacağınız açıkça görülebilir .

Bir çözüm, tüm döngü bileşenlerinizi 100 ile çarpmak olabilir, böylece daha sonra bunları kullandığınız yerde buna izin verir:

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

Bunun k=400;k<542;k+=2potansiyel kayan nokta aritmetik sorunlarından da kaçınmasıyla birlikte en iyi çözüm olduğunu düşünüyorum .
Huygens

1
Döngüdeki her yineleme için bir boru (çıktısını okumak için bc) oluşturduğunuza, bir işlemi çatacağınıza, geçici bir dosya oluşturduğunuza (here-string için), içinde çalıştıracağınıza bc(yürütülebilir ve paylaşılan kitaplıkların yüklenmesi anlamına gelir ve başlatarak), bekleyin ve temizleyin. bcDöngüyü yapmak için bir kez koşmak çok daha verimli olurdu.
Stéphane Chazelas

@ StéphaneChazelas evet, kabul etti. Ancak bu darboğazsa, muhtemelen kodu yanlış dilde yazıyoruz. Daha az verimsiz OOI (!)? i=$(bc <<< "scale...")veyai=$(echo "scale..." | bc)
roaima

1
Hızlı testimden, boru sürümü zsh'de (nereden <<<geliyor) daha hızlı bashve ksh. Başka bir kabuğa bashgeçmenin, diğer sözdizimini kullanmaktan daha iyi bir performans artışı sağlayacağını unutmayın.
Stéphane Chazelas

(ve kabukların en desteği olduğu <<<(zsh, mksh, ksh93, yash) de kayan nokta Arithmetics (destek zsh, ksh93, yash)).
Stéphane Chazelas

18

Kabuklardaki ilmeklerden kaçının.

Aritmetik yapmak istiyorsanız, awkveya kullanın bc:

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

Veya

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

Not o awk(aksine için bc) sizin işlemciler ile çalışan doublenokta sayısı temsilini (olasılıkla kayan IEEE 754 tipi). Sonuç olarak, bu sayılar bu ondalık sayıların ikili yaklaşımları olduğundan, bazı sürprizleriniz olabilir:

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

Bir eklerseniz OFMT="%.17g"eksiklerin nedenini görebilirsiniz 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 keyfi hassasiyette olduğundan, bu tür bir problem yaşamaz.

Varsayılan olarak (çıktı biçimini açık biçim belirtimleriyle değiştirmediyseniz OFMTveya printfaçık biçim belirtimleriyle awkkullanmıyorsanız ), %.6gkayan nokta sayılarını görüntülemek için kullandığını , bu nedenle 1.000.000'un üzerindeki kayan nokta sayıları için 1e6 ve üstüne geçeceğini ve yüksek sayılar için kesirli kısmı keseceğini unutmayın (100000.02) 100000 olarak görüntülenir).

Eğer gerçekten mesela o döngünün her okunması için belirli komutları çalıştırmak istiyorum, çünkü, bir kabuk döngü kullanmak ya gibi nokta aritmetik desteği kayan bir kabuk kullanmak gerekiyorsa zsh, yashya ksh93yukarıdaki gibi tek bir komut ile değerler listesi veya oluşturmak (veya seqvarsa) ve çıktısı üzerinden döngü yapın.

Sevmek:

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

Veya:

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

işlemcinizin kayan nokta sayılarının sınırlarını seqzorlamazsanız, kayan nokta yaklaşımlarının neden olduğu hataları awkyukarıdaki sürümden daha zarif bir şekilde işler .

Eğer yoksa seq, (a GNU komut), gibi bir fonksiyonu olarak daha güvenilir bir tane yapabilirsiniz:

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

Bu gibi şeyler için daha iyi çalışır seq 100000000001 0.000000001 100000000001.000000005. Bununla birlikte, keyfi olarak yüksek hassasiyete sahip sayılara sahip olmanın, bunları desteklemeyen komutlara geçireceğimizde çok yardımcı olmayacağını unutmayın.


Awk kullanımını takdir ediyorum! +1
Pandya

unset IFSİlk örnekte neden ihtiyacınız var ?
user1717828

@ user1717828, ideal olarak, bu bölünmüş + glob çağrısı ile yeni satır karakterlerine ayrılmak istiyoruz. Bunu ile yapabiliriz, IFS=$'\n'ancak bu tüm mermilerde işe yaramıyor. Veya IFS='<a-litteral-newline-here>'bu çok okunaklı değil. Veya $ IFS varsayılan değeriyle elde ettiğiniz gibi (IFS ayarını kaldırırsanız ve burada da çalışırsanız) bunun yerine kelimeleri (boşluk, sekme, yeni satır) bölebiliriz.
Stéphane Chazelas

@ user1717828: uğraşmak zorunda değiliz IFS, çünkü çıktının bölünmekten kaçınmamız seqgereken boşluklar olmadığını biliyoruz. Bu örneğin IFS, farklı bir liste oluşturma komutu için önemli olabileceğini anladığınızdan emin olmak için çoğunlukla oradasınız .
Peter Cordes

1
@PeterCordes, orada, bu yüzden IFS'nin önceden neye ayarlandığını varsaymamıza gerek yok.
Stéphane Chazelas


0

Diğerlerinin önerdiği gibi, bc'yi kullanabilirsiniz:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.