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!


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


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


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

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.


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

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

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.


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

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...

@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

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

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.


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

@ 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 

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

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 ...


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

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.