Come confrontare due numeri in virgola mobile in Bash?


156

Sto cercando di confrontare due numeri in virgola mobile all'interno di uno script bash. Devo variabili, ad es

let num1=3.17648e-22
let num2=1.5

Ora, voglio solo fare un semplice confronto di questi due numeri:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

Sfortunatamente, ho dei problemi con il giusto trattamento del num1 che può essere del "formato elettronico". :(

Qualsiasi aiuto, suggerimenti sono i benvenuti!


2
Con "formato elettronico" intendo la notazione esponenziale (chiamata anche notazione scientifica)
Jonas,

Risposte:


181

Più convenientemente

Questo può essere fatto più comodamente usando il contesto numerico di Bash:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  
fi

Spiegazione

Il piping tramite il comando di base della calcolatrice bcrestituisce 1 o 0.

L'opzione -lè equivalente a --mathlib; carica la libreria matematica standard.

Racchiudere l'intera espressione tra parentesi doppie (( ))tradurrà questi valori rispettivamente in true o false.

Assicurati che bcsia installato il pacchetto di calcolatrice di base.

Questo funziona ugualmente per i galleggianti in formato scientifico, a condizione che Evenga utilizzata una lettera maiuscola , ad esnum1=3.44E6


1
Stesso problema di stackoverflow.com/questions/8654051/… ad es. $ Echo "1.1 + 2e + 02" | bc (standard_in) 1: errore di sintassi
Nemo

1
@MohitArora Assicurati di aver bcinstallato il pacchetto calcolatrice.
Serge Stroobandt,

1
Ottengo una 0: not founddichiarazione if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Stephane

1
A tutti coloro che ottengono il "comando non trovato", ricordati che è necessario racchiuderlo bcin entrambi i backtick o $()e quindi in (( ))... cioè (( $(bc -l<<<"$a>$b") ))e non (( bc -l<<<"$a>$b" )).
Normadize

@Nemo Scrivi numeri in notazione scientifica con una lettera maiuscola Ee tutti gli errori di sintassi spariranno .
Serge Stroobandt,

100

bash gestisce solo i numeri interi ma è possibile utilizzare il bccomando come segue:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

Nota che il segno esponente deve essere maiuscolo


3
sì, ma per ovviare a calcoli errati è necessario inserire 'e' in maiuscolo nella notazione numerica scientifica e utilizzare -l flag per programmare bc per routine matematiche predefinite
alrusdi,

2
dovresti farlo notare nella tua risposta, invece di pubblicare una soluzione molto simile e non menzionare le differenze importanti.
Daniel Persson,

4
Non è una soluzione molto simile . La soluzione di Alrusdi utilizza lo bcstrumento ed è quello che consiglierei a qualsiasi programmatore BASH. BASH è un linguaggio non tipizzato. Sì, può fare l'aritmetica intera, ma per il virgola mobile è necessario utilizzare uno strumento esterno. BC è il migliore perché è quello per cui è stato creato.
DejanLekic,

8
Dal momento che sta cercando di usarlo in un'istruzione if, lo dimostrerei. se [$ (... | bc -l) == 1]; poi ...
Robert Jacobs,

27

È meglio usare awkper la matematica non intera. È possibile utilizzare questa funzione di utilità bash:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

E chiamalo come:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679

2
mi piace questa risposta, le persone tendono a scansarsi dai principianti di awk esp, sembrano pensare che sia più difficile di quanto non sia in realtà, penso che le persone vengano intimidite dalle parentesi graffe e dall'apparentemente sintassi mista linguistica (a colpo d'occhio). E poiché awk è praticamente garantito per essere presente anche sul sistema di destinazione, proprio come bc (non sono sicuro di quale, eventualmente, NON sia mai stato installato). Adoro lo script bash ma che nessun punto in virgola mobile, nemmeno un povero 2 decimali (suppongo che qualcuno potrebbe scrivere un wrapper 'falso' per quello), è davvero fastidioso ...
osirisgothra

2
L'uso awke bcnegli script di shell è una pratica standard fin dai tempi antichi, direi che alcune funzionalità non sono mai state aggiunte alle shell perché sono disponibili in awk, bc e altri strumenti Unix. Non c'è bisogno di purezza negli script di shell.
Piokuc,

1
@WanderingMind Un modo per farlo sarebbe quello di passare lo 0 o 1 in exitmodo che Awk comunichi il risultato alla shell in modo corretto e leggibile da una macchina. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... comunque nota come la condizione è invertita (lo stato di uscita 0 significa successo alla shell).
Tripleee

1
Perchè proprio python. Hai perlinstallato per impostazione predefinita su molti sistemi Linux / Unix .. anche phpanche
anubhava

1
Questa awksoluzione è più robusta nel mio caso rispetto a quella bcche restituisce risultati errati per un motivo che non ho ottenuto.
MBR

22

Soluzione bash pura per il confronto di float senza notazione esponenziale, zeri iniziali o finali:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

L'ordine degli operatori logici è importante . Le parti intere vengono confrontate come numeri e le parti frazionarie vengono confrontate intenzionalmente come stringhe. Le variabili sono divise in parti intere e frazionarie usando questo metodo .

Non confronterà i float con gli interi (senza punto).


15

puoi usare awk combinato con una bash se la condizione, awk stamperà 1 o 0 e quelli saranno interpretati dalla clausola if con vero o falso .

if awk 'BEGIN {print ('$d1' >= '$d2')}'; then
    echo "yes"
else 
    echo "no"
fi

L'uso di awk è fantastico poiché è in grado di gestire numeri in virgola mobile, ma personalmente preferisco il sintatticoif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt,

7

attenzione quando si confrontano i numeri che sono versioni del pacchetto, come verificare se grep 2.20 è maggiore della versione 2.6:

$ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
YES

Ho risolto questo problema con tale funzione shell / awk:

# get version of GNU tool
toolversion() {
    local prog="$1" operator="$2" value="$3" version

    version=$($prog --version | awk '{print $NF; exit}')

    awk -vv1="$version" -vv2="$value" 'BEGIN {
        split(v1, a, /\./); split(v2, b, /\./);
        if (a[1] == b[1]) {
            exit (a[2] '$operator' b[2]) ? 0 : 1
        }
        else {
            exit (a[1] '$operator' b[1]) ? 0 : 1
        }
    }'
}

if toolversion grep '>=' 2.6; then
   # do something awesome
fi

Su un sistema basato su Debian, dpkg --compare-versionsè spesso utile. Ha la logica completa per confrontare le versioni dei pacchetti Debian integrate, che sono più complesse di un semplice x.y.
Neil Mayhew,

5

Ovviamente, se non hai bisogno di un'aritmetica in virgola mobile, solo un'aritmetica su valori del dollaro dove ci sono sempre esattamente due cifre decimali, potresti semplicemente rilasciare il punto (moltiplicando effettivamente per 100) e confrontare gli interi risultanti.

if [[ $((10#${num1/.})) < $((10#${num2/.})) ]]; then
    ...

Ciò ovviamente richiede di essere sicuri che entrambi i valori abbiano lo stesso numero di cifre decimali.


3

Ho usato le risposte da qui e le ho messe in una funzione, puoi usarle in questo modo:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Una volta chiamato, echo $resultsarà 1in questo caso, altrimenti 0.

La funzione:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    __FUNCTION_RETURN="${result}"
}

O una versione con output di debug:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    echo "... is_first_floating_number_bigger: result is: ${result}"

    if [ "$result" -eq 0 ]; then
        echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
    else
        echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
    fi

    __FUNCTION_RETURN="${result}"
}

Basta salvare la funzione in un .shfile separato e includerla in questo modo:

. /path/to/the/new-file.sh

3

Stavo pubblicando questo come risposta a https://stackoverflow.com/a/56415379/1745001 quando è stato chiuso come duplice di questa domanda, quindi eccolo qui come si applica anche qui:

Per semplicità e chiarezza basta usare awk per i calcoli in quanto è uno strumento UNIX standard e quindi è altrettanto probabile che sia presente come bc e molto più facile lavorare sintatticamente.

Per questa domanda:

$ cat tst.sh
#!/bin/bash

num1=3.17648e-22
num2=1.5

awk -v num1="$num1" -v num2="$num2" '
BEGIN {
    print "num1", (num1 < num2 ? "<" : ">="), "num2"
}
'

$ ./tst.sh
num1 < num2

e per quell'altra domanda che è stata chiusa come duplice di questo:

$ cat tst.sh
#!/bin/bash

read -p "Operator: " operator
read -p "First number: " ch1
read -p "Second number: " ch2

awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
BEGIN {
    if ( ( op == "/" ) && ( ch2 == 0 ) ) {
        print "Nope..."
    }
    else {
        print ch1 '"$operator"' ch2
    }
}
'

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 2
2.25

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 0
Nope...

@DudiBoy no, è chiaro, semplice, codice awk portatile o codice shell + bc non ovvio, oscuro, dipendente dalla shell.
Ed Morton,

3

awke strumenti simili (ti sto fissando sed...) dovrebbero essere relegati nella pattumiera di vecchi progetti, con un codice che tutti hanno troppa paura di toccare poiché è stato scritto in un linguaggio che non legge mai.

Oppure sei il progetto relativamente raro che deve dare la priorità all'ottimizzazione dell'utilizzo della CPU rispetto all'ottimizzazione della manutenzione del codice ... nel qual caso, continua.

Se no, però, perché non usare semplicemente qualcosa di leggibile ed esplicito, come python? I tuoi compagni programmatori e il tuo sé futuro ti ringrazieranno. Puoi usare pythoninline con bash proprio come tutti gli altri.

num1=3.17648E-22
num2=1.5
if python -c "exit(0 if $num1 < $num2 else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi

@Witiko La mia versione originale era un po 'più snella.
CivFan,

Ancora più sintetico: usa not(...)invece di0 if ... else 1
Neil Mayhew il

1
Se stai relegando awk e sed (ti guardo CivFan) nella pattumiera della storia, sei un pessimo amministratore di sistema e stai digitando troppo codice. (E mi piace e uso Python, quindi non si tratta di questo). -1 per snarkiness fuori posto. C'è un posto nel dominio dei sistemi per quegli strumenti, Python o no.
Mike S,

1
È interessante notare che ho finito con il buon vecchio Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Vai tranquillo. Ogni lingua ha il suo posto.
Mike S,

1
Non imbrogliare con la stranezza sintattica di seds. A differenza di Python, awk è un'utilità obbligatoria su ogni installazione UNIX e l'equivalente awk di python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"è semplicemente awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ed Morton,

2

Questo script può aiutare dove sto controllando se la grailsversione installata è maggiore del minimo richiesto. Spero che sia d'aiuto.

#!/bin/bash                                                                                         

min=1.4                                                                                             
current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`                         

if [ 1 -eq `echo "${current} < ${min}" | bc` ]                                                          
then                                                                                                
    echo "yo, you have older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi

2
num1=0.555
num2=2.555


if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
       echo "$num1 is greater then $num2"
else
       echo "$num2 is greater then $num1"
fi

2

si prega di controllare il codice modificato di seguito: -

#!/bin/bash

export num1=(3.17648*e-22)
export num2=1.5

st=$((`echo "$num1 < $num2"| bc`))
if [ $st -eq 1 ]
  then
    echo -e "$num1 < $num2"
  else
    echo -e "$num1 >= $num2"
fi

questo funziona bene.


2

Una soluzione che supporta tutte le possibili notazioni, compresa la notazione scientifica con esponenti sia maiuscoli che minuscoli (ad es. 12.00e4):

if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
then
    echo "$value1 is below $value2"
fi 

1

Usa la shell korn, in bash potresti dover confrontare la parte decimale separatamente

#!/bin/ksh
X=0.2
Y=0.2
echo $X
echo $Y

if [[ $X -lt $Y ]]
then
     echo "X is less than Y"
elif [[ $X -gt $Y ]]
then
     echo "X is greater than Y"
elif [[ $X -eq $Y ]]
then
     echo "X is equal to Y"
fi

2
il problema è che molte distribuzioni non arrivano con ksh installato, e se il tuo script verrà usato da altri, loro tendono a non amare installare cose extra, specialmente quando è solo uno script che dovrebbe essere scritto in bash -uno penserebbe che non avevano bisogno di un'altra shell per farlo, il che mina in primo luogo l'intera ragione dell'uso di uno script bash - sicuro che potremmo anche andare a codificarlo in C ++, ma perché?
Osirisgothra,

Quali sono le distribuzioni che vengono fornite senza ksh installato?
Piokuc,

1
@piokuc per esempio, Ubuntu Desktop & Server. Direi che è piuttosto importante ...
Olli il

Inoltre, la domanda richiede specificamente una soluzione che funzioni in bash. Potrebbero esserci davvero delle buone ragioni per questo. Supponiamo che faccia parte di un'applicazione di grandi dimensioni e che non sia possibile migrare tutto su ksh. Oppure è in esecuzione su una piattaforma integrata in cui l'installazione di un'altra shell è davvero un problema.
Olli,

1

Usando bashj ( https://sourceforge.net/projects/bashj/ ), un mutante bash con supporto java, basta scrivere (ed è facile da leggere):

#!/usr/bin/bashj

#!java
static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}

#!bashj
num1=3.17648e-22
num2=1.5
comp=j.doubleCompare($num1,$num2)
if [ $comp == 0 ] ; then echo "Equal" ; fi
if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi

Naturalmente l'ibridazione bashj bash / java offre molto di più ...


0

Cosa ne pensi di questo? = D

VAL_TO_CHECK="1.00001"
if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
    echo "$VAL_TO_CHECK >= 1"
else
    echo "$VAL_TO_CHECK < 1"
fi

1
Lo script Awk dovrebbe semplicemente exit 0riportare la verità e exit 1restituire false; allora puoi semplificare in modo straordinariamente elegante if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (ancora più elegante se incapsuli lo script Awk in una funzione shell).
Tripleee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.