Chcę porównać dwie liczby zmiennoprzecinkowe w skrypcie powłoki. Poniższy kod nie działa:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Chcę porównać dwie liczby zmiennoprzecinkowe w skrypcie powłoki. Poniższy kod nie działa:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Odpowiedzi:
Można osobno sprawdzić części całkowite i ułamkowe:
#!/bin/bash
min=12.45
val=12.35
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then
min=$val
fi
echo $min
Jak zauważył Fered w komentarzach, działa to tylko wtedy, gdy obie liczby mają części ułamkowe, a obie części ułamkowe mają tę samą liczbę cyfr. Oto wersja, która działa dla liczb całkowitych lub ułamkowych i dowolnego operatora bash:
#!/bin/bash
shopt -s extglob
fcomp() {
local oldIFS="$IFS" op=$2 x y digitx digity
IFS='.' x=( ${1##+([0]|[-]|[+])}) y=( ${3##+([0]|[-]|[+])}) IFS="$oldIFS"
while [[ "${x[1]}${y[1]}" =~ [^0] ]]; do
digitx=${x[1]:0:1} digity=${y[1]:0:1}
(( x[0] = x[0] * 10 + ${digitx:-0} , y[0] = y[0] * 10 + ${digity:-0} ))
x[1]=${x[1]:1} y[1]=${y[1]:1}
done
[[ ${1:0:1} == '-' ]] && (( x[0] *= -1 ))
[[ ${3:0:1} == '-' ]] && (( y[0] *= -1 ))
(( ${x:-0} $op ${y:-0} ))
}
for op in '==' '!=' '>' '<' '<=' '>='; do
fcomp $1 $op $2 && echo "$1 $op $2"
done
1.00000000000000000000000001
jest większe niż 2
.
Bash nie rozumie arytmetyki zmiennoprzecinkowej. Liczby zawierające przecinek dziesiętny traktuje jak ciągi znaków.
Zamiast tego użyj awk lub bc.
#!/bin/bash
min=12.45
val=10.35
if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then
min=${val}
fi
echo "$min"
Jeśli zamierzasz wykonywać wiele operacji matematycznych, prawdopodobnie lepiej polegać na Pythonie lub Perlu.
Możesz użyć pakietu num-utils do prostych manipulacji ...
Aby uzyskać więcej informacji na temat matematyki, zobacz ten link ... Opisuje kilka opcji, np.
Przykład numprocess
echo "123.456" | numprocess /+33.267,%2.33777/
# 67.0395291239087
A programs for dealing with numbers from the command line
The 'num-utils' are a set of programs for dealing with numbers from the
Unix command line. Much like the other Unix command line utilities like
grep, awk, sort, cut, etc. these utilities work on data from both
standard in and data from files.
Includes these programs:
* numaverage: A program for calculating the average of numbers.
* numbound: Finds the boundary numbers (min and max) of input.
* numinterval: Shows the numeric intervals between each number in a sequence.
* numnormalize: Normalizes a set of numbers between 0 and 1 by default.
* numgrep: Like normal grep, but for sets of numbers.
* numprocess: Do mathematical operations on numbers.
* numsum: Add up all the numbers.
* numrandom: Generate a random number from a given expression.
* numrange: Generate a set of numbers in a range expression.
* numround: Round each number according to its value.
Oto bash
hack ... Dodaje wiodące zera do liczby całkowitej, aby sensowne było porównywanie ciągów od lewej do prawej. Ten konkretny fragment kodu wymaga, aby zarówno min, jak i val faktycznie miały kropkę dziesiętną i co najmniej jedną cyfrę dziesiętną.
min=12.45
val=10.35
MIN=0; VAL=1 # named array indexes, for clarity
IFS=.; tmp=($min $val); unset IFS
tmp=($(printf -- "%09d.%s\n" ${tmp[@]}))
[[ ${tmp[VAL]} < ${tmp[MIN]} ]] && min=$val
echo min=$min
wydajność:
min=10.35
Do prostych obliczeń na liczbach zmiennoprzecinkowych (+ - * / i porównaniach) możesz użyć awk.
min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')
Lub, jeśli masz ksh93 lub zsh (nie bash), możesz użyć wbudowanej arytmetyki powłoki, która obsługuje liczby zmiennoprzecinkowe.
if ((min>val)); then ((val=min)); fi
Aby uzyskać bardziej zaawansowane obliczenia zmiennoprzecinkowe, wyszukaj temat bc . W rzeczywistości działa na liczbach punktów o dowolnej dokładności.
Polecenie sort
ma opcję -g
( --general-numeric-sort
), której można użyć do porównań <
„mniej niż” lub >
„większy niż” poprzez znalezienie minimum lub maksimum.
Te przykłady znajdują minimum:
$ printf '12.45\n10.35\n' | sort -g | head -1
10.35
Działa z dość ogólną notacją liczb zmiennoprzecinkowych, jak w przypadku notacji E.
$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10
Zwróć uwagę na to E-10
, że pierwsza liczba jest 0.000000001245
rzeczywiście mniejsza niż 10.35
.
Standard zmiennoprzecinkowy, IEEE754 , definiuje niektóre wartości specjalne. Dla tych porównań interesujące są INF
dla nieskończoności. Istnieje również ujemna nieskończoność; Obie są dobrze zdefiniowanymi wartościami w standardzie.
$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF
Aby znaleźć maksymalne wykorzystanie sort -gr
zamiast sort -g
, odwracanie kolejności sortowania:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
Aby wdrożyć porównanie <
(„mniejsze niż”), aby można go było stosować w if
itp., Porównaj minimum z jedną z wartości. Jeśli minimum jest równe wartości, w porównaniu z tekstem , jest mniejsze niż inna wartość:
$ a=12.45; b=10.35
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
1
$ a=12.45; b=100.35
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
0
a == min(a, b)
jest takie samo jak a <= b
. Warto zauważyć, że nie sprawdza to dokładnie mniej niż chociaż. Jeśli chcesz to zrobić, musisz sprawdzić a == min(a, b) && a != max(a, b)
, innymi słowya <= b and not a >= b
Wystarczy użyć ksh
( ksh93
dokładnie) lub zsh
, które oba natywnie obsługują arytmetykę zmiennoprzecinkową:
$ cat test.ksh
#!/bin/ksh
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo "$min"
$ ./test.ksh
10.35
Edycja: Przepraszam, że tęskniłem ksh93
już zaproponowano. Zachowując moją odpowiedź, aby wyjaśnić, skrypt zamieszczony w pytaniu otwierającym może być używany bez żadnych zmian poza przełącznikiem powłoki.
Edycja2: Zauważ, że ksh93
wymaga , aby zawartość zmiennej była zgodna z twoimi ustawieniami regionalnymi, tj. W przypadku francuskich ustawień regionalnych, zamiast kropki należy użyć przecinka:
...
min=12,45
val=10,35
...
Bardziej niezawodnym rozwiązaniem jest ustawienie ustawień regionalnych na początku skryptu, aby upewnić się, że będzie on działał niezależnie od ustawień regionalnych użytkownika:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(więc nie w połowie świata, w którym znajduje się separator dziesiętny ,
). zsh
nie ma tego problemu.
LC_ALL
, co oznacza również, że liczby nie będą wyświetlane (lub wprowadzane) w preferowanym przez użytkownika formacie. Potencjalnie lepsze podejście można znaleźć na stronie unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/ ...
.
tak.
min=$(echo "${min}sa ${val}d la <a p" | dc)
To używa dc
kalkulatora do zerwania s
wartości $min
w rejestrze a
i d
umieszcza wartość $val
na szczycie głównego stosu wykonawczego. Następnie l
umieszcza zawartość a
na górze stosu, w którym to momencie wygląda następująco:
${min} ${val} ${val}
<
Wyskakuje szczycie dwa wpisy od stosu i porównuje je. Tak więc stos wygląda następująco:
${val}
Jeśli górna pozycja była mniejsza niż druga od góry, wypycha zawartość a
na górę, więc stos wygląda następująco:
${min} ${val}
W przeciwnym razie nic nie robi, a stos nadal wygląda:
${val}
Następnie po prostu p
sprawdza, czy wpis najwyższego stosu jest zaznaczony.
Więc dla twojego problemu:
min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.35
Ale:
min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.45
Dlaczego nie użyć starego, dobrego expr
?
Przykładowa składnia:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Dla prawdziwych wyrażeń kod wyjścia expr wynosi 0, a ciąg „1” jest wysyłany do standardowego wyjścia. Odwróć dla fałszywych wyrażeń.
Sprawdziłem to z GNU i FreeBSD 8 expr.
expr 1.09 '<' -1.1
wydrukuje 1
i wyjdzie za pomocą 0
(sukces).
Aby sprawdzić, czy dwie (ewentualnie ułamkowe) liczby są w porządku, sort
jest (w miarę rozsądnie) przenośne:
min=12.45
val=12.55
if { echo $min ; echo $val ; } | sort -n -c 2>/dev/null
then
echo min is smallest
else
echo val is smallest
fi
Jeśli jednak chcesz aktualizować minimalną wartość, nie potrzebujesz if
. Sortuj liczby i zawsze używaj pierwszego (najmniejszego):
min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
Zwykle robię podobne rzeczy z osadzonym kodem python:
#!/bin/sh
min=12.45
val=10.35
python - $min $val<<EOF
if ($min > $val):
print $min
else:
print $val
EOF
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
0.5
i0.06
). Lepiej użyj narzędzia, które już rozumie zapis dziesiętny.