Wie rundet man Dezimalstellen mit bc in bash?

45

Ein kurzes Beispiel für die Verwendung von Bash-Skripten:

#!/bin/bash
echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
echo "scale=2; $float/1.18" |bc -l
read -p "Press any key to continue..."
bash scriptname.sh

Angenommen, der Preis ist: 48,86 Die Antwort lautet: 41,406779661 (41,40 tatsächlich, weil ich verwende scale=2;)

Meine Frage lautet: Wie runde ich die zweite Dezimalstelle, um die Antwort auf diese Weise anzuzeigen ?: 41.41

blackedx
quelle
Ich finde es seltsam, weil "printf"% 0.2f \ n "41.445" jetzt funktioniert, "printf"% 0.2f \ n "41.435 und" printf "% 0.2f \ n" 41.455 ". Auch Ihr eigener Fall funktioniert (Am 12.04), aber nicht mit der .445
Luis Alvarado
8
IMHO, niemand hat diese Frage zufriedenstellend beantwortet , vielleicht weil bcnicht erreicht werden kann, was angefordert wird (oder zumindest die Frage, die ich gestellt habe, als ich diesen Beitrag gefunden habe), wie Dezimalzahlen gerundet werden könnenbc (das heißt zufällig von bash).
Adam Katz
Ich weiß, dass diese Frage alt ist, aber ich kann keinem Kommentar widerstehen, der meine eigene Implementierung von bc vorantreibt : github.com/gavinhoward/bc . Dafür steht es in der eingebauten Bibliothek mit der r()Funktion, so dass Sie es nutzen können bc -le "r($float/1.18, 2)".
Gavin Howard

Antworten:

31

Eine Bash-Round-Funktion:

round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

In Ihrem Codebeispiel verwendet:

#!/bin/bash
# the function "round()" was taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# the round function:
round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
#echo "scale=2; $float/1.18" |bc -l
echo $(round $float/1.18 2);
read -p "Press any key to continue..."

Viel Glück: o)

user85321
quelle
1
Übrigens, wenn die Zahl negativ ist, müssen wir-0.5
Aquarius Power
Konnte das Beispiel nicht auf der Testkonsole zum Laufen bringen! "Debugging" hat ein wenig ergeben, dass $2 = 3ich dafür zB verwenden musste echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Beachten Sie die Umgebungsbedingungen vor dem Drucken! Dies wird Ihnen wieder beibringen, dass es immer wichtig ist zu verstehen, was Sie von woanders kopieren.
Syntaxfehler
@Aquarius Power danke für die Inspiration! Ich habe jetzt eine Version des obigen Skripts erstellt, die sowohl mit negativen als auch mit positiven Zahlen funktioniert.
Syntaxfehler
Dies ist unnötig kompliziert und erfordert eine zusätzliche Berechnung, um zu den einfacheren Antworten von Migas und Zuberuber zu gelangen.
Adam Katz
@AquariusPower +1 Ja, bei negativen Zahlen ist das anders. Um auf eine negative (nicht ganzzahlige!) Zahl zu testen, habe ich es ein wenig versucht und mich mit dem if [ Echo "$ 1/1" | zufrieden gegeben bc` -gt 0] `- gibt es eine elegantere Möglichkeit zu prüfen (außer die Zeichenfolge nach" - "zu analysieren)?
RobertG
30

Einfachste Lösung:

printf %.2f $(echo "$float/1.18" | bc -l)
Migas
quelle
2
Wie auch in einem obigen Kommentar ( askubuntu.com/a/179949/512213 ) angemerkt , würde sich dies bei negativen Zahlen leider falsch verhalten.
RobertG
2
@RobertG: Warum? Diese Lösung wird nicht verwendet +0.5. Versuchen Sie printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"und Sie werden 41.41und bekommen -41.41.
Musiphil
1
Auch mit Pfeife möglich:echo "$float/1.18" | bc -l | xargs printf %.2f
Dessert
Die Lösung in dieser Antwort gibt mir: bash: printf: 1.69491525423728813559: invalid numberje nach Gebietsschema.
Torsten Bronger
19

Bash / awk-Rundung:

echo "23.49" | awk '{printf("%d\n",$1 + 0.5)}'  

Wenn Sie Python haben, können Sie so etwas verwenden:

echo "4.678923" | python -c "print round(float(raw_input()))"
zuberuber
quelle
Danke für den Tipp, aber er löst nicht das, was ich brauche. Ich muss es nur mit bash tun ...
blackedx
1
Der Python-Befehl ist besser lesbar und eignet sich für schnelle Skripte. Unterstützt auch die Rundung beliebiger Ziffern durch Hinzufügen von zB ", 3" zur Rundungsfunktion. Vielen Dank
Elle
echo "$float" |awk '{printf "%.2f", $1/1.18}'führt die angeforderte Mathematik der Frage mit der angeforderten Genauigkeit von Hundertsteln durch. Das ist so viel "mit bash" wie der bcAufruf in der Frage.
Adam Katz
2
Für Python können Sie einfach so etwas wie python -c "print(round($num))"where verwenden num=4.678923. Es gibt keinen Grund, sich mit stdin herumzuschlagen. Sie können auch rund um n Stellen wie folgt: python -c "print(round($num, $n))".
Sechs
Ich habe mit dieser Lösung etwas ziemlich Ordentliches geschafft, mein Problem war 0,5, in diesem Fall habe ich nicht immer die Runde bekommen, die ich brauchte, also habe ich Folgendes gefunden: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Wann wird + auf- und abrunden).
Yaron
4

Hier ist eine rein bc Lösung. Rundungsregeln: Bei +/- 0,5 von Null abrunden.

Geben Sie den gewünschten Maßstab in $ result_scale ein. Ihre Mathematik sollte dort stehen, wo sich $ MATH in der bc-Befehlsliste befindet:

bc <<MATH
h=0
scale=0

/* the magnitude of the result scale */
t=(10 ^ $result_scale)

/* work with an extra digit */
scale=$result_scale + 1

/* your math into var: m */
m=($MATH)

/* rounding and output */
if (m < 0) h=-0.5
if (m > 0) h=0.5

a=(m * t + h)

scale=$result_scale
a / t
MATH
MetroEast
quelle
1
sehr interessant! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 wie hier erklärt :)
Aquarius Power
Ich nominiere dies als "beste Antwort".
Marc Tamsky
2

Ich weiß, es ist eine alte Frage, aber ich habe eine reine 'bc'-Lösung ohne' wenn 'oder Zweige:

#!/bin/sh
bcr()
{
    echo "scale=$2+1;t=$1;scale-=1;(t*10^scale+((t>0)-(t<0))/2)/10^scale" | bc -l
}

Verwenden Sie es wie bcr '2/3' 5oder bcr '0.666666' 2-> (Ausdruck gefolgt von Skala)

Das ist möglich, weil es in bc (wie C / C ++) erlaubt ist, logische Ausdrücke in Ihre Berechnungen zu mischen. Der Ausdruck ((t>0)-(t<0))/2)wird abhängig vom Vorzeichen von 't' mit +/- 0,5 bewertet und verwendet daher den richtigen Wert zum Runden.

Marcus Kolenda
quelle
Das sieht ordentlich aus, bcr "5 / 2" 0kehrt aber 2stattdessen zurück 3. Vermisse ich etwas?
blujay
1
#!/bin/bash
# - loosely based on the function "round()", taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# - inspired by user85321 @ askubuntu.com (original author)
#   and Aquarius Power

# the round function (alternate approach):

round2()
{
    v=$1
    vorig=$v
    # if negative, negate value ...
    (( $(bc <<<"$v < 0") == 1 )) && v=$(bc <<<"$v * -1")
    r=$(bc <<<"scale=$3;(((10^$3)*$v/$2)+0.5)/(10^$3)")

    # ... however, since value was only negated to get correct rounding, we 
    # have to add the minus sign again for the resulting value ...

    (( $(bc <<< "$vorig < 0") == 1 )) && r=$(bc <<< "$r * -1")
    env printf %.$3f $r
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
round2 $float 1.18 2
echo && read -p "Press any key to continue..."

Es ist eigentlich ganz einfach: Es ist nicht erforderlich, explizit eine fest codierte "-0,5" -Variante für negative Zahlen hinzuzufügen. Mathematisch gesprochen berechnen wir nur die absoluten Wert des Arguments und addieren trotzdem 0,5, wie wir es normalerweise tun würden. Da wir (leider) keine eingebaute abs()Funktion zur Verfügung haben (es sei denn, wir codieren eine), werden wir das Argument einfach negieren , wenn es negativ ist.

Außerdem erwies es sich als sehr umständlich, mit dem zu arbeiten Quotienten als Parameter zu arbeiten (da ich für meine Lösung auf Dividende und Divisor getrennt zugreifen müssen). Aus diesem Grund hat mein Skript einen zusätzlichen dritten Parameter.

Syntax-Fehler
quelle
@muru über deine letzten Änderungen: Herestrings statt echo .. |Shell-Arithmetik, worum geht es echo $()? Dies ist leicht zu erklären: Ihre Here-Strings benötigen immer ein beschreibbares/tmp Verzeichnis! Meine Lösung funktioniert auch in einer schreibgeschützten Umgebung, z. B. einer Notfall-Root-Shell, in der standardmäßig nicht immer schreibgeschützt /ist . Es gab also einen guten Grund, warum ich es so codiert habe.
Syntaxfehler
Ich stimme Herestrings zu. Aber wann wird das echo $()jemals gebraucht? Das und die Einrückung haben meine Änderungen veranlasst, die Herestrings sind einfach passiert.
Muru
@muru Nun, der sinnloseste Fall, der echo $()überfällig war, um behoben zu werden, war in der Tat die vorletzte Zeile, lol. Danke für die Warnung. Zumindest sieht dieser jetzt viel besser aus, das kann ich nicht leugnen. Jedenfalls habe ich die Logik darin geliebt. Viele boolesche Sachen, weniger überflüssige "wenn" s (die den Code mit Sicherheit um 50 Prozent in die Luft jagen würden).
Syntaxfehler
1

Ich bin noch auf der Suche nach einem reinen bc Antwort, um nur einen Wert innerhalb einer Funktion zu runden, aber hier ist eine reine bashAntwort:

#!/bin/bash

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"

embiggen() {
  local int precision fraction=""
  if [ "$1" != "${1#*.}" ]; then  # there is a decimal point
    fraction="${1#*.}"       # just the digits after the dot
  fi
  int="${1%.*}"              # the float as a truncated integer
  precision="${#fraction}"   # the number of fractional digits
  echo $(( 10**10 * $int$fraction / 10**$precision ))
}

# round down if negative
if [ "$float" != "${float#-}" ]
  then round="-5000000000"
  else round="5000000000"
fi

# calculate rounded answer (sans decimal point)
answer=$(( ( `embiggen $float` * 100 + $round ) / `embiggen 1.18` ))

int=${answer%??}  # the answer as a truncated integer

echo $int.${answer#$int}  # reassemble with correct precision

read -p "Press any key to continue..."

Grundsätzlich werden dabei die Dezimalstellen sorgfältig extrahiert und alles mit 100 Milliarden multipliziert (10¹⁰, 10**10 in bash) , auf Genauigkeit und Rundung korrigiert, die eigentliche Division ausgeführt, auf die entsprechende Größe zurückgeteilt und die Dezimalstelle erneut eingefügt.

Schritt für Schritt:

Die embiggen()Funktion weist die abgeschnittene Ganzzahlform ihres Arguments zu $intund speichert die Zahlen nach dem Punkt in $fraction. Die Anzahl der Nachkommastellen ist in angegeben $precision. Die Mathematik multipliziert 10¹⁰ mit der Verkettung von $intund $fractionpasst diese dann an die Genauigkeit an (z. B. embiggen 48.86wird 10¹⁰ × 4886000000004886/100 und ergibt 488.600.000.000).

Wir wollen eine endgültige Genauigkeit von Hundertsteln, also multiplizieren wir die erste Zahl mit 100, addieren 5 zu Rundungszwecken und dividieren dann die zweite Zahl. Diese Zuordnung $answerlässt uns hundertmal die endgültige Antwort.

Jetzt müssen wir den Dezimalpunkt hinzufügen. Wir weisen $intdem $answerAusschluss der letzten beiden Ziffern einen neuen Wert zu , dann den echomit einem Punkt und dem$answer Ausschluss des $intWerts, für den bereits gesorgt wurde. (Egal, welcher Syntax-Highlighting-Fehler dies wie einen Kommentar erscheinen lässt.)

(Bashismus: Potenzierung ist nicht POSIX, also ist dies ein Bashismus. Eine reine POSIX-Lösung würde Schleifen erfordern, um Nullen hinzuzufügen, anstatt Zehnerpotenzen zu verwenden. Außerdem ist "embiggen" ein perfekt kromulantes Wort.)


Einer der Hauptgründe, warum ich zshals Shell verwende, ist die Unterstützung von Gleitkomma-Mathematik. Die Lösung dieser Frage liegt auf der Hand in zsh:

printf %.2f $((float/1.18))

(Ich würde gerne jemanden sehen, der dieser Antwort einen Kommentar mit dem Trick hinzufügt, Gleitkomma-Arithmetik zu aktivieren bash, aber ich bin mir ziemlich sicher, dass es eine solche Funktion noch nicht gibt.)

Adam Katz
quelle
1

Wenn Sie das Ergebnis haben, ziehen Sie beispielsweise 2.3747888 in Betracht

alles was du tun musst, ist:

d=$(echo "(2.3747888+0.5)/1" | bc); echo $d

Dies rundet die Zahl richtig. Beispiel:

(2.49999 + 0.5)/1 = 2.99999 

Die Dezimalstellen werden durch entfernt bcund so auf 2 abgerundet, wie es sein sollte

user525061
quelle
1

Ich musste die Gesamtdauer einer Sammlung von Audiodateien berechnen.

Also musste ich:

A. Ermitteln der Dauer für jede Datei ( nicht gezeigt )

B. addiere alle Dauern (sie waren jeweils in NNN.NNNNNN(fp) Sekunden)

C. getrennte Stunden, Minuten, Sekunden, Sekunden.

D. Ausgabe einer Folge von HR: MIN: SEC: FRAMES, wobei frame = 1/75 sec.

(Frames stammen aus dem in Studios verwendeten SMPTE-Code.)


A: benutze ffprobeund parse die Dauerzeile in eine fp Nummer (nicht gezeigt)

B:

 # add them up as a series of strings separated by "+" and send it to bc

arr=( "${total[@]}" )  # copy array

# IFS is "Internal Field Separator"
# the * in arr[*] means "all of arr separated by IFS" 
# must have been made for this
IFS='+' sum=$(echo "scale=3; ${arr[*]} "| bc -l)# (-l= libmath for fp)
echo $sum 

C:

# subtract each amount of time from tt and store it    
tt=$sum   # tt is a running var (fp)


hrs=$(echo "$tt / 3600" | bc)

tt=$(echo "$tt - ( $hrs * 3600 )" | bc )

min=$(echo "$tt / 60" | bc )

tt=$(echo "$tt - ($min *60)" | bc )

sec=$(echo "$tt/1" | bc )

tt=$(echo "$tt - $sec" | bc )

frames=$(echo "$tt * 75"  | bc ) # 75 frames /sec 
frames=$(echo "$frames/1" | bc ) # truncate to whole #

D:

#convert to proper format with printf (bash builtin)        
hrs=$(printf "%02d\n" $hrs)  # format 1 -> 01 

min=$(printf "%02d\n" $min)

sec=$(printf "%02d\n" $sec)

frames=$(printf "%02d\n" $frames)

timecode="$hrs:$min:$sec:$frames"

# timecode "01:13:34:54"
Chris Reid
quelle
1

Hier ist eine abgekürzte Version Ihres Skripts, die korrigiert wurde, um die gewünschte Ausgabe zu erzielen:

#!/bin/bash
float=48.86
echo "You asked for $float; This is the price without taxes:"
echo "scale=3; price=$float/1.18 +.005; scale=2; price/1 " | bc

Beachten Sie, dass das Aufrunden auf die nächste Ganzzahl gleichbedeutend ist mit dem Hinzufügen von 0,5 und dem Ermitteln des Floors oder dem Abrunden (für positive Zahlen).

Außerdem wird der Skalierungsfaktor zum Zeitpunkt des Betriebs angewendet. Also (dies sind bcBefehle, die Sie in Ihr Terminal einfügen können):

float=48.86; rate=1.18; 
scale=2; p2=float/rate
scale=3; p3=float/rate
scale=4; p4=float/rate
print "Compare:  ",p2, " v ", p3, " v ", p4
Compare:  41.40 v 41.406 v 41.4067

# however, scale does not affect an entered value (nor addition)
scale=0
a=.005
9/10
0
9/10+a
.005

# let's try rounding
scale=2
p2+a
41.405
p3+a
41.411
(p2+a)/1
41.40
(p3+a)/1
41.41
toddkaufmann
quelle
1

Reine BC-Implementierung wie gewünscht

define ceil(x) { auto os,xx;x=-x;os=scale;scale=0 xx=x/1;if(xx>x).=xx-- scale=os;return(-xx) }

Wenn Sie das in eine Datei namens functions.bc schreiben, können Sie mit abrunden

echo 'ceil(3.1415)' | bc functions.bc

Code für die BC-Implementierung finden Sie unter http://phodd.net/gnu-bc/code/funcs.bc

Aethalide
quelle
0
bc() {
        while :
        do
                while IFS='$\n' read i
                do
                        /usr/bin/bc <<< "scale=2; $i" | sed 's/^\./0&/'
                done
        done
}
sjas
quelle
0

Sie können awk verwenden, keine Rohre erforderlich:

$> PRICE=48.86
$> awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}"
41.41

Stellen Sie den Preis vorher mit der Umgebungsvariablen ein PRICE=<val>.

  • Dann call the awk - $PRICEgeht als awk-Variable in awk ein price.

  • Es wird dann mit +.005 auf die nächste 100 aufgerundet.

  • Die Formatierungsoption printf %.2fbegrenzt den Maßstab auf zwei Dezimalstellen.

Wenn Sie dies viel tun müssen, ist dieser Weg viel effizienter als die ausgewählte Antwort:

time for a in {1..1000}; do echo $(round 48.86/1.18 2) > /dev/random; done
real    0m8.945s
user    0m6.067s
sys 0m4.277s

time for a in {1..1000}; do awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}">/dev/random; done
real    0m2.628s
user    0m1.462s
sys 0m1.275s
A.Danischewski
quelle