pętla uderzeniowa z przyrostem 0,02


11

Chcę zrobić pętlę for w bashu z 0,02 jako przyrosty, których próbowałem

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

ale to nie zadziałało.


9
Bash nie wykonuje obliczeń zmiennoprzecinkowych.
jordanm

1
można zwiększyć bc, ale zatrzymanie na 4.52 może być trudne. skorzystaj z sugestii @roaima, zastosuj pomocniczy var z krokiem 2, i użyji=$(echo $tmp_var / 100 | bc)
Archemar


5
Zwykle nie chcesz używać liczb zmiennoprzecinkowych jako indeksu pętli . Kumulujesz błąd przy każdej iteracji.
isanae

Odpowiedzi:


18

Czytanie bash strony podręcznika daje następujące informacje:

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

Po pierwsze, wyrażenie arytmetyczne expr1jest oceniane zgodnie z zasadami opisanymi poniżej w części OCENA ARYTMETYCZNA. [...]

a następnie otrzymujemy tę sekcję

OCENA ARYTMETYCZNA

Powłoka umożliwia wyrażeń arytmetycznych być oceniane w określonych warunkach (patrz let, a declarewbudowane polecenia i arytmetycznego). Oceny dokonuje się w liczbach całkowitych o stałej szerokości bez sprawdzania przepełnienia [...]

Widać więc wyraźnie, że nie można użyć forpętli z wartościami innymi niż całkowite.

Jednym z rozwiązań może być po prostu pomnożenie wszystkich składników pętli przez 100, pozwalając na to, gdzie będziesz ich później używać, na przykład:

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

Myślę, że jest to najlepsze rozwiązanie, k=400;k<542;k+=2ponieważ pozwala uniknąć potencjalnych problemów arytmetycznych zmiennoprzecinkowych.
Huygens

1
Zauważ, że dla każdej iteracji w pętli tworzysz potok (do odczytu wyniku bc), rozwidlasz proces, tworzysz plik tymczasowy (dla ciągu tutaj), wykonujesz bcw nim (co oznacza ładowanie bibliotek wykonywalnych i współdzielonych oraz inicjowanie ich), poczekaj na to i posprzątaj. Uruchomienie bcraz w celu wykonania pętli byłoby znacznie bardziej wydajne.
Stéphane Chazelas,

@ StéphaneChazelas tak, zgodził się. Ale jeśli jest to wąskie gardło, prawdopodobnie i tak piszemy kod w niewłaściwym języku. OOI, który jest mniej nieefektywny (!)? i=$(bc <<< "scale...")lubi=$(echo "scale..." | bc)
roaima,

1
Z mojego szybkiego testu, wersja potoku jest szybsza w Zsh (skąd <<<pochodzi), bashi ksh. Zauważ, że przejście na inną powłokę niż bashda lepszy wzrost wydajności niż użycie innej składni.
Stéphane Chazelas

(i większość pocisków, że wsparcie <<<(zsh, mksh, ksh93, Yash) również wspierać pływających arytmetyki Point ( zsh, ksh93, yash)).
Stéphane Chazelas

18

Unikaj pętli w muszlach.

Jeśli chcesz wykonywać arytmetykę, użyj awklub bc:

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

Lub

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

Zauważ, że awk(w przeciwieństwie do bc) współpracuje z procesorami doublereprezentacji liczb zmiennoprzecinkowych (prawdopodobnie typu IEEE 754 ). W rezultacie, ponieważ te liczby są binarnymi przybliżeniami tych liczb dziesiętnych, możesz mieć pewne niespodzianki:

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

Jeśli dodasz OFMT="%.17g", możesz zobaczyć przyczynę braku 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 wykonuje dowolną precyzję, więc nie ma tego rodzaju problemów.

Zauważ, że domyślnie (chyba że zmienisz format wyjściowy za pomocą OFMTlub użyjesz printfjawnych specyfikacji formatu), awkużywa %.6gdo wyświetlania liczb zmiennoprzecinkowych, więc przełącza się na 1e6 i więcej dla liczb zmiennoprzecinkowych powyżej 1 000 000 i obcina część ułamkową dla wysokich liczb (100000,02 będzie wyświetlany jako 100000).

Jeśli naprawdę potrzebujesz użyć pętli powłoki, ponieważ na przykład chcesz uruchomić określone polecenia dla każdej iteracji tej pętli, użyj powłoki z obsługą arytmetyki zmiennoprzecinkowej zsh, yashlub, ksh93lub wygeneruj listę wartości za pomocą jednego polecenia, jak powyżej (lub seqjeśli jest dostępny) i zapętlić jego wynik.

Lubić:

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

Lub:

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

o ile nie przekroczysz limitów liczb zmiennoprzecinkowych procesora, obsługa seqbłędów wynikających z przybliżeń zmiennoprzecinkowych jest bardziej płynna niż w przypadku awkwersji powyżej.

Jeśli nie masz seq(polecenia GNU), możesz uczynić je bardziej niezawodnym jako funkcja:

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

To działałoby lepiej w przypadku takich rzeczy seq 100000000001 0.000000001 100000000001.000000005. Zauważ jednak, że posiadanie liczb z dowolnie wysoką precyzją niewiele pomoże, jeśli przekażemy je poleceniom, które ich nie obsługują.


Doceniam użycie awk! +1
Pandya

Dlaczego potrzebujesz unset IFSw pierwszym przykładzie?
user1717828,

@ user1717828, najlepiej, przy tym wywołaniu split + glob, chcemy podzielić znaki nowej linii. Możemy to zrobić za pomocą, IFS=$'\n'ale to nie działa we wszystkich powłokach. Lub, IFS='<a-litteral-newline-here>'ale to nie jest bardzo czytelne. Lub możemy zamiast tego podzielić na słowa (spacja, tabulator, nowa linia), tak jak w przypadku domyślnej wartości $ IFS lub jeśli odłączysz IFS i również tutaj działa.
Stéphane Chazelas

@ user1717828: nie musimy z tym zadzierać IFS, ponieważ wiemy, że na seqwyjściu nie ma spacji, w których musimy uniknąć podziału. Ma to na celu przede wszystkim upewnienie się, że ten przykład zależy od tego IFS, co może mieć znaczenie dla innej komendy generującej listę.
Peter Cordes,

1
@PeterCordes, jest tam, więc nie musimy przyjmować żadnych założeń dotyczących wcześniejszego ustawienia IFS.
Stéphane Chazelas,


0

Jak sugerują inni, możesz użyć bc:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.