Wie verwende ich die Gleitkommadivision in Bash?

242

Ich versuche, zwei Bildbreiten in einem Bash-Skript zu teilen, aber Bash gibt mir 0als Ergebnis:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Ich habe den Bash-Leitfaden studiert und weiß, dass ich ihn bcin allen Beispielen im Internet verwenden sollte bc. In echoich die gleiche Sache in meinem zu setzen versucht , SCALEaber es hat nicht funktioniert.

Hier ist das Beispiel, das ich in den Tutorials gefunden habe:

echo "scale=2; ${userinput}" | bc 

Wie kann ich Bash dazu bringen, mir einen Schwimmer zu geben 0.5?

Medya Gh
quelle
9
Ein Kommentar für alle, die versuchen, Gleitkomma-Arithmetik in Ihrem Skript auszuführen. Fragen Sie sich: Brauche ich wirklich Gleitkomma-Arithmetik? manchmal kann man wirklich ohne auskommen. Siehe zum Beispiel den letzten Teil von BashFAQ / 022 .
gniourf_gniourf

Antworten:

242

Das kannst du nicht. Bash macht nur ganze Zahlen; Sie müssen an ein Tool wie z bc.

Ignacio Vazquez-Abrams
quelle
8
Wie kann ich ein Tool wie bc delegieren, um die Antwort in die Variable RESULT einzufügen?
Medya Gh
2
Sie meinen also wie VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh
54
@ Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)oder VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")ohne $ (()) (doppelte Klammern); die durch die Bash vor der Ausführung des Befehls erweitert wird
Nahuel Fouilleul
4
Stimmt, aber es ist im Allgemeinen wahrscheinlicher, dass awk bereits im System installiert ist.
CMCDragonkai
9
@NahuelFouilleul Hier hast du die beste Antwort. Sollte wirklich seine eigene Antwort sein und als Antwort akzeptiert werden. Diese spezielle Zeile war unglaublich nützlich: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David
197

du kannst das:

bc <<< 'scale=2; 100/3'
33.33

UPDATE 20130926 : Sie können verwenden:

bc -l <<< '100/3' # saves a few hits
aayoubi
quelle
8
... und fügt viele Ziffern hinzu. Zumindest auf meiner Maschine ergibt dies 33.3333333333333333333333, während die erstere 33.33 ergibt.
Andreas Spindler
12
@AndreasSpindler Eine Art alter Beitrag, aber falls jemand es wissen möchte, kann dies durch Anwenden des Befehls scale geändert werden, z. bc -l <<< 'scale=2; 100/3'
Martie
Passen Sie einfach auf, wenn Sie hoffen, eine Ganzzahl für die spätere Verwendung in Bash mithilfe von scale = 0 zu erhalten. Ab Version 1.06.95 ignoriert bc aus irgendeinem Grund die Skalierungsvariable, wenn die eingegebenen Zahlen einen Dezimalteil haben. Vielleicht steht das in der Dokumentation, aber ich konnte es nicht finden. Versuchen Sie: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell
1
@ GregBell Die Manpage sagt Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.Und es gibt einen zusätzlichen Hinweis für den /Bediener:The scale of the result is the value of the variable scale.
Schmied
2
Danke @psmith. Interessanterweise heißt es für "die Skala des Ergebnisses ist der Wert der variablen Skala", nicht jedoch für die Multiplikation. Meine besseren Beispiele: bc <<< 'scale=1; 1*3.00001' Skala ist aus irgendeinem Grund wirklich 5, bc <<< 'scale=1; 1/3.000001' Skala ist 1. Interessanterweise setzt das Teilen durch 1 es gerade: bc <<< 'scale=1; 1*3.00001/1'Skala ist 1
Greg Bell
136

Bash

Wie von anderen angemerkt, bashunterstützt keine Gleitkomma-Arithmetik, obwohl Sie sie mit einem Trick mit festen Dezimalstellen vortäuschen könnten, z. B. mit zwei Dezimalstellen:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Ausgabe:

.33

Siehe Nilfreds Antwort für einen ähnlichen, aber präziseren Ansatz.

Alternativen

Neben den genannten bcund awkAlternativen gibt es auch folgende:

clisp

clisp -x '(/ 1.0 3)'

mit bereinigter Ausgabe:

clisp --quiet -x '(/ 1.0 3)'

oder durch stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

Genius Cli Rechner

echo 1/3.0 | genius

Ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

Gnuplot

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

Oder:

jq -n 1/3

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

oder durch stdin:

echo 'print(1/3)' | lua

Maxima

echo '1/3,numer;' | maxima

mit bereinigter Ausgabe:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

Knoten

echo 1/3 | node -p

Oktave

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R.

echo 1/3 | R --no-save

mit bereinigter Ausgabe:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

Rubin

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Mit bereinigter Ausgabe:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

echo 'print $(( 1/3. ))' | zsh

Einheiten

units 1/3

Mit kompakter Ausgabe:

units --co 1/3

Andere Quellen

Stéphane Chazelas beantwortete eine ähnliche Frage unter Unix.SX.

Thor
quelle
9
Gute Antwort. Ich weiß, dass es einige Jahre nach der Frage veröffentlicht wurde, aber es verdient mehr, die akzeptierte Antwort zu sein.
Brian Cline
2
Wenn Sie zsh zur Verfügung haben, dann lohnt es sich, Ihr Skript in zsh anstelle von Bash zu schreiben
Andrea Corbellini
Daumen hoch für Gnuplot :)
andywiecko
Schöne Liste. Besonders echo 1/3 | node -pist kurz.
Johnny Wong
39

Die Antwort von Marvin ein wenig verbessern:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc kommt nicht immer als installiertes Paket.

adrianlzt
quelle
4
Das awk-Skript benötigt ein exit, um zu verhindern, dass es aus seinem Eingabestream liest. Ich schlage auch vor, awk- -vFlaggen zu verwenden, um das Leaning-Zahnstochersyndrom zu verhindern. Also:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
Aecolley
2
Ein schwierigerer Weg, dies zu tun, wäre das Lesen der Argumente aus dem Eingabestream : RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
JMster
1
bcist Teil von POSIX und wird normalerweise vorinstalliert.
Fuz
Dies funktionierte für mich mit Git Bash in Windows 7 ... danke :)
The Beast
31

Sie können bc mit der -lOption (dem L-Buchstaben) verwenden.

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
user1314742
quelle
4
Wenn ich das nicht -lin mein System einbeziehe, führt bc keine Gleitkomma-Mathematik durch.
Starbeamrainbowlabs
28

Alternativ zu bc können Sie awk in Ihrem Skript verwenden.

Beispielsweise:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

Oben weist "% .2f" die Funktion printf an, eine Gleitkommazahl mit zwei Ziffern nach der Dezimalstelle zurückzugeben. Ich habe Echo verwendet, um die Variablen als Felder weiterzuleiten, da awk sie ordnungsgemäß verarbeitet. "$ 1" und "$ 2" beziehen sich auf das erste und zweite Feld, die in awk eingegeben werden.

Und Sie können das Ergebnis als eine andere Variable speichern, indem Sie:

RESULT = `echo ...`
Marvin
quelle
Ausgezeichnet! Vielen Dank. Dies ist hilfreich für eingebettete Umgebungen, in denen bc nicht vorhanden ist. Sie haben mir Cross-Compilation-Zeit gespart.
Enthusiastgeek
19

Es ist die perfekte Zeit, um zsh, eine (fast) Bash-Obermenge, mit vielen zusätzlichen netten Funktionen, einschließlich Gleitkomma-Mathematik, auszuprobieren. So würde Ihr Beispiel in zsh aussehen:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Dieser Beitrag kann Ihnen helfen: bash - Lohnt es sich, für den gelegentlichen Gebrauch auf zsh zu wechseln?

Penghe Geng
quelle
3
Ich bin ein großer Fan von zsh und benutze es seit 4 Jahren, aber die interaktive Nutzung ist hier ein guter Schwerpunkt. Ein Skript, das zsh erfordert, ist normalerweise nicht sehr portabel für eine Vielzahl von Maschinen, da es normalerweise leider nicht Standard ist (um fair zu sein, vielleicht ist das in Ordnung; OP hat nicht genau gesagt, wie es verwendet wird).
Brian Cline
17

Nun, bevor float eine Zeit war, in der feste Dezimalstellenlogik verwendet wurde:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

Die letzte Zeile ist ein Bashim. Wenn Sie Bash nicht verwenden, versuchen Sie stattdessen diesen Code:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

Das Grundprinzip des Codes lautet: Multiplizieren Sie mit 100, bevor Sie dividieren, um 2 Dezimalstellen zu erhalten.

Nilfred
quelle
5

Wenn Sie die gewünschte Variante gefunden haben, können Sie sie auch in eine Funktion einbinden.

Hier wickle ich etwas Bashismus in eine Div-Funktion ein:

Einzeiler:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

Oder mehrzeilig:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Jetzt haben Sie die Funktion

div <dividend> <divisor> [<precision=2>]

und benutze es gerne

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

UPDATE Ich habe ein kleines Skript hinzugefügt, das Ihnen die grundlegenden Operationen mit Gleitkommazahlen für Bash bietet:

Verwendung:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

Und hier das Drehbuch:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}
bebbo
quelle
1
local _d=${3:-2}ist einfacher
KamilCuk
4

Es ist nicht wirklich Gleitkomma, aber wenn Sie etwas wollen, das mehr als ein Ergebnis in einem Aufruf von bc setzt ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

berechnet die Fläche und den Durchmesser eines Kreises, dessen Radius in $ 1 angegeben ist

user3210339
quelle
4

Es gibt Szenarien, in denen Sie bc nicht verwenden können, da es möglicherweise einfach nicht vorhanden ist, wie in einigen reduzierten Versionen von Busybox oder eingebetteten Systemen. In jedem Fall ist es immer eine gute Sache, äußere Abhängigkeiten zu begrenzen. Sie können der Zahl, die durch (Zähler) geteilt wird, immer Nullen hinzufügen. Dies entspricht dem Multiplizieren mit einer Potenz von 10 (Sie sollten eine Potenz von 10 entsprechend wählen die Genauigkeit, die Sie benötigen), wodurch die Division eine ganzzahlige Zahl ausgibt. Sobald Sie diese Ganzzahl haben, behandeln Sie sie als Zeichenfolge und positionieren Sie den Dezimalpunkt (von rechts nach links verschieben) mehrmals entsprechend der Zehnerpotenz, mit der Sie den Zähler multipliziert haben. Dies ist eine einfache Methode, um Float-Ergebnisse zu erhalten, indem nur ganzzahlige Zahlen verwendet werden.

Daniel J.
quelle
1
Sogar Busybox hat Awk. Vielleicht sollte es hier eine prominentere Awk-Antwort geben.
Tripleee
4

Während Sie in Bash keine Gleitkommadivision verwenden können, können Sie die Festkommadivision verwenden. Alles, was Sie tun müssen, ist, Ihre ganzen Zahlen mit einer Potenz von 10 zu multiplizieren und dann den ganzzahligen Teil zu dividieren und eine Modulo-Operation zu verwenden, um den gebrochenen Teil zu erhalten. Nach Bedarf runden.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))
G. Allen Morris III
quelle
4

Ich weiß, es ist alt, aber zu verlockend. Die Antwort lautet also: Du kannst nicht ... aber du kannst es irgendwie. Lass uns das versuchen:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

Auf diese Weise erhalten Sie 2 Ziffern nach dem Punkt, abgeschnitten (nennen Sie es Rundung nach unten, haha) in reiner Bash (keine Notwendigkeit, andere Prozesse zu starten). Wenn Sie nach dem Punkt nur eine Ziffer benötigen, multiplizieren Sie natürlich mit 10 und machen Modulo 10.

was das macht:

  • Das erste $ ((...)) macht eine ganzzahlige Division.
  • Das zweite $ ((...)) macht eine ganzzahlige Division für etwas 100-mal Größeres, wobei im Wesentlichen Ihre 2 Ziffern links vom Punkt verschoben werden und dann (%) Sie nur diese 2 Ziffern erhalten, indem Sie Modulo ausführen.

Bonustrack : Die bc-Version x 1000 dauerte auf meinem Laptop 1,8 Sekunden, während die reine Bash-Version 0,016 Sekunden dauerte.

івась тарасик
quelle
3

So führen Sie Gleitkommaberechnungen in Bash durch:

Anstatt " here strings " ( <<<) mit dem bcBefehl zu verwenden, wie es eines der am häufigsten bewerteten Beispiele tut, ist hier mein bevorzugtes bcGleitkomma-Beispiel direkt aus dem EXAMPLESAbschnitt der bcManpages (siehe man bcfür die Handbuchseiten).

Bevor wir beginnen, wissen Sie, dass eine Gleichung für pi lautet : pi = 4*atan(1). a()unten ist die bcmathematische Funktion für atan().

  1. So speichern Sie das Ergebnis einer Gleitkommaberechnung in einer Bash-Variablen - in diesem Fall in einer aufgerufenen Variablenpi . Beachten Sie, dass scale=10in diesem Fall die Anzahl der Dezimalstellen mit einer Genauigkeit von 10 festgelegt wird. Nach dieser Stelle stehende Dezimalstellen werden abgeschnitten .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Um nun eine einzige Codezeile zu haben, die auch den Wert dieser Variablen ausgibt, fügen Sie den echoBefehl einfach wie folgt als Folgebefehl am Ende hinzu. Beachten Sie die Anweisung mit 10 Dezimalstellen, wie befohlen:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Lassen Sie uns zum Schluss eine Rundung einwerfen. Hier verwenden wir die printfFunktion, um auf 4 Dezimalstellen zu runden. Beachten Sie, dass die 3.14159...Runden jetzt zu 3.1416. Da wir runden, müssen wir nicht mehr scale=10auf 10 Dezimalstellen kürzen, also entfernen wir einfach diesen Teil. Hier ist die Endlösung:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Hier ist eine weitere wirklich großartige Anwendung und Demo der oben genannten Techniken: Messen und Drucken der Laufzeit.

Beachten Sie, dass dt_minvon gerundet wird 0.01666666666...zu 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Verbunden:

Gabriel Staples
quelle
1
solide Antwort mit detaillierten Beispielen und Anwendungsfällen & Runde mit printf
downvoteit
2

Verwenden Sie calc. Es ist das einfachste Beispiel, das ich gefunden habe:

berechne 1 + 1

 2

berechnet 1/10

 0.1
Camila Pereira
quelle
2

Für diejenigen, die versuchen, Prozentsätze mit der akzeptierten Antwort zu berechnen, aber an Genauigkeit verlieren:

Wenn Sie dies ausführen:

echo "scale=2; (100/180) * 180" | bc

Sie erhalten 99.00nur, was an Präzision verliert.

Wenn Sie es so ausführen:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Jetzt verstehst du 99.99.

Weil Sie nur zum Zeitpunkt des Druckens skalieren.

Siehe hier

Wadih M.
quelle
1

** Injektionssichere Gleitkomma-Mathematik in Bash / Shell **

Hinweis: Der Schwerpunkt dieser Antwort liegt auf der Bereitstellung von Ideen für eine injektionssichere Lösung für die Durchführung von Mathematik in Bash (oder anderen Shells). Dies kann natürlich mit geringfügigen Anpassungen verwendet werden, um eine erweiterte Zeichenfolgenverarbeitung usw. durchzuführen.

Die meisten der vorgestellten Lösungen erstellen ein kleines Scriptlet im laufenden Betrieb unter Verwendung externer Daten (Variablen, Dateien, Befehlszeile, Umgebungsvariablen). Die externe Eingabe kann verwendet werden, um schädlichen Code in die Engine einzufügen, viele davon

Nachfolgend finden Sie einen Vergleich zur Verwendung der verschiedenen Sprachen zur Durchführung grundlegender mathematischer Berechnungen, wobei das Ergebnis Gleitkommawerte sind. Es berechnet A + B * 0,1 (als Gleitkomma).

Bei allen Lösungsversuchen wird vermieden, dass dynamische Scriptlets erstellt werden, die extrem schwer zu warten sind. Stattdessen verwenden sie ein statisches Programm und übergeben Parameter an die angegebene Variable. Sie können sicher mit Parametern mit Sonderzeichen umgehen, wodurch die Möglichkeit der Code-Injection verringert wird. Die Ausnahme ist 'BC', das keine Eingabe- / Ausgabefunktion bietet

Die Ausnahme ist 'bc', das keine Eingabe / Ausgabe liefert, alle Daten kommen über Programme in stdin und alle Ausgaben gehen an stdout. Alle Berechnungen werden in einer Sandbox ausgeführt, die keine Nebenwirkungen zulässt (Öffnen von Dateien usw.). Theoretisch spritzwassergeschützt!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"
dash-o
quelle
für Python3 mit beliebigen Argumenten: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness
0

Hier ist der Befehl awk: -F = Feldtrennzeichen == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
koolwithk
quelle