Wie vergleiche ich zwei Gleitkommazahlen in Bash?

156

Ich bemühe mich sehr, zwei Gleitkommazahlen in einem Bash-Skript zu vergleichen. Ich muss Variablen, z

let num1=3.17648e-22
let num2=1.5

Jetzt möchte ich nur einen einfachen Vergleich dieser beiden Zahlen durchführen:

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

Leider habe ich einige Probleme mit der richtigen Behandlung der num1, die vom "E-Format" sein kann. :((

Jede Hilfe, Hinweise sind willkommen!

Jonas
quelle
2
Mit "E-Format" meine ich die Exponentialnotation (auch wissenschaftliche Notation genannt)
Jonas

Antworten:

180

Bequemer

Dies kann bequemer unter Verwendung des numerischen Kontexts von Bash erfolgen:

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

Erläuterung

Das Durchleiten des Basisrechnerbefehls bcgibt entweder 1 oder 0 zurück.

Die Option -lentspricht --mathlib; Es lädt die Standard-Mathematikbibliothek.

Wenn Sie den gesamten Ausdruck in doppelte Klammern setzen, (( ))werden diese Werte in wahr bzw. falsch übersetzt.

Bitte stellen Sie sicher, dass das bcBasisrechnerpaket installiert ist.

Dies gilt auch für Floats im wissenschaftlichen Format, sofern ein Großbuchstabe verwendet Ewird, znum1=3.44E6

Serge Stroobandt
quelle
1
Gleiches Problem wie stackoverflow.com/questions/8654051/… zB $ echo "1.1 + 2e + 02" | bc (standard_in) 1: Syntaxfehler
Nemo
1
@MohitArora Bitte stellen Sie sicher, dass Sie das bcTaschenrechnerpaket installiert haben.
Serge Stroobandt
1
Ich bekomme eine 0: not foundmit der Aussage if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Stephane
1
bcDenken Sie bei allen, die "Befehl nicht gefunden" erhalten, daran, dass Sie die entweder in Backticks oder $()und dann in (( ))... dh (( $(bc -l<<<"$a>$b") ))und nicht einschließen müssen (( bc -l<<<"$a>$b" )).
Normadize
@Nemo Schreiben Sie Zahlen in wissenschaftlicher Notation mit einem Großbuchstaben E, und alle Syntaxfehler sind verschwunden.
Serge Stroobandt
100

bash behandelt nur ganzzahlige Mathematik, aber Sie können den bcBefehl wie folgt verwenden:

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

Beachten Sie, dass das Exponentenzeichen in Großbuchstaben geschrieben werden muss

alrusdi
quelle
3
Ja, aber um falsche Berechnungen zu
umgehen, muss das
2
Sie sollten dies in Ihrer Antwort darauf hinweisen, anstatt nur eine sehr ähnliche Lösung zu veröffentlichen und die wichtigen Unterschiede nicht zu erwähnen.
Daniel Persson
4
Es ist keine sehr ähnliche Lösung. Die Lösung von Alrusdi verwendet das bcTool und das würde ich jedem BASH-Programmierer empfehlen. BASH ist eine typenlose Sprache. Ja, es kann ganzzahlige Arithmetik ausführen, aber für Gleitkommazahlen müssen Sie ein externes Werkzeug verwenden. BC ist das Beste, denn dafür ist es gemacht.
DejanLekic
8
Da er versucht, es in einer if-Anweisung zu verwenden, würde ich das zeigen. if [$ (... | bc -l) == 1]; dann ...
Robert Jacobs
27

Es ist besser, awkfür nicht ganzzahlige Mathematik zu verwenden. Sie können diese Bash-Dienstprogrammfunktion verwenden:

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

Und nenne es als:

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
Anubhava
quelle
2
Ich mag diese Antwort, die Leute neigen dazu, sich vor Anfängern zu scheuen, sie scheinen zu denken, dass es schwieriger ist als es tatsächlich ist. Ich denke, die Leute lassen sich von den geschweiften Klammern und der scheinbar sprachlich gemischten Syntax einschüchtern (auf einen Blick). Und da awk so ziemlich garantiert auch auf dem Zielsystem vorhanden ist, genau wie bc (nicht sicher, welches, falls vorhanden, jemals NICHT installiert ist). Ich liebe Bash-Skripte, aber dass kein Gleitkomma, nicht einmal magere 2 Dezimalstellen (ich denke, jemand könnte einen 'falschen' Wrapper dafür schreiben), wirklich nervt ...
osirisgothra
2
Die Verwendung von awkund bcin Shell-Skripten ist seit der Antike eine Standardpraxis. Ich würde sagen, dass einige Funktionen Shells nie hinzugefügt wurden, da sie in awk, bc und anderen Unix-Tools verfügbar sind. Keine Notwendigkeit für Reinheit in Shell-Skripten.
Piokuc
1
@WanderingMind Eine Möglichkeit, dies zu tun, besteht darin, die 0 oder 1 an zu übergeben, exitdamit Awk das Ergebnis auf ordnungsgemäße, maschinenlesbare Weise an die Shell zurückgibt. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... beachten Sie jedoch, wie die Bedingung invertiert wird (der Exit-Status 0 bedeutet Erfolg für die Shell).
Tripleee
1
Warum gerade python. Sie haben perlstandardmäßig auf vielen Linux / Unix-Systemen installiert .. sogar phpauch
anubhava
1
Diese awkLösung ist in meinem Fall robuster als die mit der bc, die aus einem Grund, den ich nicht erhalten habe, falsche Ergebnisse liefert.
MBR
22

Reine Bash-Lösung zum Vergleichen von Floats ohne Exponentialschreibweise, führende oder nachfolgende Nullen:

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

Die Reihenfolge der logischen Operatoren ist wichtig . Ganzzahlige Teile werden als Zahlen und Bruchteile absichtlich als Zeichenfolgen verglichen. Mit dieser Methode werden Variablen in ganzzahlige und gebrochene Teile aufgeteilt .

Floats werden nicht mit ganzen Zahlen (ohne Punkt) verglichen.

Benutzer
quelle
15

Sie können awk in Kombination mit einer bash if-Bedingung verwenden. awk gibt 1 oder 0 aus und diese werden durch die if-Klausel mit true oder false interpretiert .

if awk 'BEGIN {print ('$d1' >= '$d2')}'; then
    echo "yes"
else 
    echo "no"
fi
ungalcrys
quelle
Die Verwendung von awk ist großartig, da es Gleitkommazahlen verarbeiten kann, aber ich persönlich bevorzuge die Synthaxif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt
7

Achten Sie beim Vergleichen von Zahlen, die Paketversionen sind, z. B. beim Überprüfen, ob grep 2.20 größer als Version 2.6 ist:

$ 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

Ich habe ein solches Problem mit einer solchen Shell / Awk-Funktion gelöst:

# 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
Elan Ruusamäe
quelle
Auf einem Debian-basierten System dpkg --compare-versionsist dies häufig nützlich. Es verfügt über die vollständige Logik zum Vergleichen der integrierten Debian-Paketversionen, die komplexer als nur sind x.y.
Neil Mayhew
5

Wenn Sie keine wirklich Gleitkomma-Arithmetik benötigen, sondern nur Arithmetik für z. B. Dollarwerte, bei denen immer genau zwei Dezimalstellen vorhanden sind, können Sie den Punkt einfach fallen lassen (effektiv mit 100 multiplizieren) und die resultierenden Ganzzahlen vergleichen.

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

Dazu müssen Sie natürlich sicherstellen, dass beide Werte die gleiche Anzahl von Dezimalstellen haben.

Tripleee
quelle
3

Ich habe die Antworten von hier verwendet und sie in eine Funktion eingefügt. Sie können sie folgendermaßen verwenden:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Einmal angerufen, echo $resultwird 1in diesem Fall anders sein 0.

Die Funktion:

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}"
}

Oder eine Version mit Debug-Ausgabe:

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}"
}

Speichern Sie die Funktion einfach in einer separaten .shDatei und fügen Sie sie folgendermaßen hinzu:

. /path/to/the/new-file.sh
Thomas Kekeisen
quelle
3

Ich habe dies als Antwort auf https://stackoverflow.com/a/56415379/1745001 gepostet, als es als Dup dieser Frage geschlossen wurde. Hier ist es also so, wie es auch hier gilt:

Verwenden Sie zur Vereinfachung und Klarheit einfach awk für die Berechnungen, da es sich um ein Standard-UNIX-Tool handelt und daher genauso wahrscheinlich vorhanden ist wie bc und viel einfacher syntaktisch zu bearbeiten ist.

Für diese Frage:

$ 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

und für diese andere Frage, die als Dup dieser geschlossen wurde:

$ 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...
Ed Morton
quelle
@DudiBoy nein, es ist klarer, einfacher, portabler awk-Code oder nicht offensichtlicher, obskurer, shell-abhängiger Shell + bc-Code.
Ed Morton
2

Dieses Skript kann hilfreich sein, wenn ich überprüfe, ob die installierte grailsVersion größer als das erforderliche Minimum ist. Ich hoffe es hilft.

#!/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
betenagupd
quelle
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
rmil
quelle
2

Bitte überprüfen Sie den unten bearbeiteten Code: -

#!/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

das funktioniert gut.

Gopika BG
quelle
2

awkund Werkzeuge wie dieses (ich starre dich an sed...) sollten in den Mülleimer alter Projekte verbannt werden, mit Code, den jeder zu sehr fürchten muss, da er in einer Sprache geschrieben wurde, die niemals gelesen werden kann.

Oder Sie sind das relativ seltene Projekt, bei dem die Optimierung der CPU-Auslastung Vorrang vor der Optimierung der Codewartung haben muss. In diesem Fall fahren Sie fort.

Wenn nicht, warum nicht stattdessen einfach etwas Lesbares und Explizites verwenden, wie z python. Ihre Mitcodierer und Ihr zukünftiges Selbst werden es Ihnen danken. Sie können pythonwie alle anderen auch Inline mit Bash verwenden.

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
CivFan
quelle
@ Witiko Meine Originalversion war etwas snarkier.
CivFan
Noch prägnanter: Verwenden Sie not(...)anstelle von0 if ... else 1
Neil Mayhew
1
Wenn Sie awk und sed (ich sehe Sie als CivFan an) in den Mülleimer der Geschichte verbannen, sind Sie ein mieser Systemadministrator und geben zu viel Code ein. (Und ich mag und benutze Python, also geht es nicht darum). -1 für verlegte Snarkiness. In der Systemdomäne gibt es einen Platz für diese Tools, Python oder nein.
Mike S
1
Interessanterweise bekam ich den guten alten Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Kinderleicht. Jede Sprache hat ihren Platz.
Mike S
1
Lassen Sie sich nicht von Seds syntakischer Verrücktheit überraschen. Im Gegensatz zu Python ist awk bei jeder UNIX-Installation ein obligatorisches Dienstprogramm, und das awk-Äquivalent von python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"ist einfach awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ed Morton
2

Eine Lösung, die alle möglichen Notationen unterstützt, einschließlich der wissenschaftlichen Notation mit Exponenten in Groß- und Kleinbuchstaben (z. B. 12.00e4):

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

Verwenden Sie Korn Shell. In Bash müssen Sie möglicherweise den Dezimalteil separat vergleichen

#!/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
Alan Joseph
quelle
2
Das Problem ist, dass viele Distributionen nicht mit installiertem ksh geliefert werden. Wenn Ihr Skript von anderen verwendet wird, müssen sie normalerweise keine zusätzlichen Dinge installieren, insbesondere wenn es sich nur um ein Skript handelt, das in Bash geschrieben werden soll - Man würde denken, dass sie dafür KEINE ANDERE Shell benötigen, was den ganzen Grund für die Verwendung eines Bash-Skripts untergräbt. - Sicher, wir könnten es AUCH in C ++ codieren, aber warum?
Osirisgothra
Was sind die Distributionen, die ohne ksh installiert sind?
Piokuc
1
@ piokuc zum Beispiel Ubuntu Desktop & Server. Ich würde sagen, es ist ziemlich wichtig ...
Olli
Außerdem fragt die Frage speziell nach einer Lösung, die in Bash funktioniert. Dafür kann es wirklich gute Gründe geben. Angenommen, es ist Teil einer großen Anwendung, und eine Migration auf ksh ist nicht möglich. Oder es läuft auf einer eingebetteten Plattform, auf der die Installation einer anderen Shell wirklich ein Problem darstellt.
Olli
1

Mit bashj ( https://sourceforge.net/projects/bashj/ ), einem Bash-Mutanten mit Java-Unterstützung, schreiben Sie einfach (und es ist leicht zu lesen):

#!/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

Natürlich bietet die Bashj Bash / Java-Hybridisierung viel mehr ...

Fil
quelle
0

Wie wäre es damit? = 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
Eduardo Lucio
quelle
1
Das Awk-Skript sollte einfach exit 0die Wahrheit melden und exit 1falsch zurückgeben. dann können Sie das bemerkenswert elegante vereinfachen if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (noch eleganter, wenn Sie das Awk-Skript in eine Shell-Funktion einkapseln).
Tripleee