Voglio confrontare due numeri in virgola mobile in uno script di shell. Il seguente codice non funziona:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Voglio confrontare due numeri in virgola mobile in uno script di shell. Il seguente codice non funziona:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Risposte:
È possibile controllare separatamente le parti intere e frazionarie:
#!/bin/bash
min=12.45
val=12.35
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then
min=$val
fi
echo $min
Come dice fered nei commenti, funziona solo se entrambi i numeri hanno parti frazionarie ed entrambe le parti frazionarie hanno lo stesso numero di cifre. Ecco una versione che funziona per intero o frazionario e qualsiasi operatore 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
è maggiore di 2
.
Bash non capisce l'aritmetica in virgola mobile. Tratta i numeri contenenti un punto decimale come stringhe.
Usa invece awk o bc.
#!/bin/bash
min=12.45
val=10.35
if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then
min=${val}
fi
echo "$min"
Se hai intenzione di fare molte operazioni matematiche, probabilmente è meglio fare affidamento su Python o Perl.
Puoi usare il pacchetto num-utils per semplici manipolazioni ...
Per la matematica più seria, vedi questo link ... Descrive diverse opzioni, ad es.
Un esempio di 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.
Ecco un bash
trucco ... Aggiunge gli 0 iniziali all'intero per rendere significativa una comparazione da sinistra a destra della stringa. Questo particolare pezzo di codice richiede che sia min che val abbiano effettivamente un punto decimale e almeno una cifra decimale.
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
produzione:
min=10.35
Per semplici calcoli su numeri in virgola mobile (+ - * / e confronti), è possibile utilizzare awk.
min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')
Oppure, se hai ksh93 o zsh (non bash), puoi usare l'aritmetica integrata della shell, che supporta i numeri in virgola mobile.
if ((min>val)); then ((val=min)); fi
Per calcoli in virgola mobile più avanzati, cercare bc . In realtà funziona su numeri di punti fissi di precisione arbitraria.
Il comando sort
ha un'opzione -g
( --general-numeric-sort
) che può essere utilizzata per i confronti su <
"minore di" o >
"maggiore di", trovando il minimo o il massimo.
Questi esempi stanno trovando il minimo:
$ printf '12.45\n10.35\n' | sort -g | head -1
10.35
Funziona con una notazione piuttosto generale di numeri in virgola mobile, come con la notazione elettronica
$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10
Nota il E-10
, rendendo il primo numero 0.000000001245
, anzi inferiore a 10.35
.
Lo standard a virgola mobile, IEEE754 , definisce alcuni valori speciali. Per questi confronti, quelli interessanti sono INF
per l'infinito. C'è anche l'infinito negativo; Entrambi sono valori ben definiti nello standard.
$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF
Per trovare il massimo utilizzo sort -gr
anziché sort -g
invertire l'ordinamento:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
Per implementare il confronto <
("minore di"), in modo che possa essere utilizzato in if
ecc., Confrontare il minimo con uno dei valori. Se il minimo è uguale al valore, rispetto al testo , è inferiore all'altro valore:
$ 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)
è lo stesso di a <= b
. Vale la pena notare che questo non controlla rigorosamente meno di quanto però. Se vuoi farlo, devi controllare a == min(a, b) && a != max(a, b)
, in altre parolea <= b and not a >= b
Basta usare ksh
( ksh93
precisamente) o zsh
, che entrambi supportano nativamente l'aritmetica in virgola mobile:
$ cat test.ksh
#!/bin/ksh
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo "$min"
$ ./test.ksh
10.35
Modifica: mi dispiace, ho perso ksh93
era già stato suggerito. Mantenere la mia risposta solo per chiarire che lo script pubblicato nella domanda iniziale può essere utilizzato senza modifiche al di fuori dell'interruttore della shell.
Modifica2: Nota che ksh93
richiede che il contenuto della variabile sia coerente con la tua locale, ovvero con una locale francese, è necessario utilizzare una virgola anziché un punto:
...
min=12,45
val=10,35
...
Una soluzione più efficace consiste nell'impostare la locale all'inizio dello script per assicurarsi che funzionerà indipendentemente dalla locale dell'utente:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(quindi non in mezzo mondo in cui si trova il separatore decimale ,
). zsh
non ha questo problema.
LC_ALL
, ciò significa anche che i numeri non verranno visualizzati (o inseriti) nel formato preferito dell'utente. Vedi unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… per un approccio potenzialmente migliore.
.
comunque.
min=$(echo "${min}sa ${val}d la <a p" | dc)
Quello usa la dc
calcolatrice per s
strappare il valore in $min
in register a
e lo d
aumenta nella $val
parte superiore del suo stack di esecuzione principale. l
Elenca quindi il contenuto della a
parte superiore della pila, a quel punto sembra:
${min} ${val} ${val}
Il <
pop espone le prime due voci dallo stack e le confronta. Quindi lo stack appare quindi:
${val}
Se la voce in alto era inferiore alla seconda in cima, spinge il contenuto a
in cima, quindi lo stack appare come:
${min} ${val}
Altrimenti non fa nulla e lo stack sembra ancora:
${val}
Quindi p
strappa semplicemente la voce dello stack superiore.
Quindi per il tuo problema:
min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.35
Ma:
min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.45
Perché non usare il vecchio, bene expr
?
Sintassi di esempio:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Per le espressioni vere , il codice di uscita expr è 0, con la stringa '1' inviata a stdout. Invertire per false espressioni.
Ho controllato questo con GNU e FreeBSD 8 expr.
expr 1.09 '<' -1.1
stamperà 1
e uscirà con 0
(successo).
Per verificare se due numeri (possibilmente frazionari) sono in ordine, sort
è (ragionevolmente) portatile:
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
Tuttavia, se si desidera effettivamente aggiornare un valore minimo, non è necessario un if
. Ordina i numeri e usa sempre il primo (minimo):
min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
Di solito faccio cose simili con il codice Python incorporato:
#!/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
e0.06
). Faresti meglio a usare uno strumento che già capisce la notazione decimale.