web-dev-qa-db-fra.com

Comment arrondir les nombres décimaux en utilisant bc dans bash?

Un exemple rapide de ce que je veux utiliser les scripts bash:

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

En supposant que le prix est: 48.86 La réponse sera: 41.406779661 (41.40 en fait car j'utilise scale=2;)

Ma question est: Comment puis-je arrondir la deuxième décimale pour afficher la réponse de cette manière?: 41.41

42
blackedx

Une fonction bash round:

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

Utilisé dans votre exemple de code:

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

Bonne chance: o)

31
user85321

La solution la plus simple:

printf %.2f $(echo "$float/1.18" | bc -l)
29
migas

Bash/awk arrondi:

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

Si vous avez python, vous pouvez utiliser quelque chose comme ceci:

echo "4.678923" | python -c "print round(float(raw_input()))"
19
zuberuber

Voici une solution purement bc. Règles d'arrondi: à +/- 0,5, arrondir à zéro.

Mettez la balance que vous cherchez dans $ result_scale; votre calcul devrait être où $ MATH est situé dans la liste de commande bc:

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
4
MetroEast

Je sais que c'est une vieille question, mais j'ai une solution pure 'bc' sans 'if' ou branches:

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

Utilisez-le comme bcr '2/3' 5 ou bcr '0.666666' 2 -> (expression suivie d'une échelle)

C'est possible parce qu'en bc (comme C/C++), il est permis de mélanger des expressions logiques dans vos calculs. L'expression ((t>0)-(t<0))/2) sera évaluée à +/- 0.5 en fonction du signe de 't' et utilisera donc la bonne valeur pour l'arrondi.

2
Marcus Kolenda

Je devais calculer la durée totale d'une collection de fichiers audio.

Donc je devais:

A. obtenir la durée de chaque fichier (non affiché)

B. additionnez toutes les durées (elles étaient chacune en NNN.NNNNNN (fp) secondes)

C. séparez les heures, les minutes, les secondes, les sous-secondes.

D. sort une chaîne de HR: MIN: SEC: FRAMES, où frame = 1/75 sec.

(Les cadres proviennent du code SMPTE utilisé dans les studios.)


A: utilisez ffprobe et la ligne de durée d'analyse dans un numéro fp (non affiché)

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 #

RÉ:

#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"
1
Chris Reid

Pure BC implémentation à la demande

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

si vous mettez cela dans un fichier appelé functions.bc, alors vous pouvez arrondir avec

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

Code pour l'implémentation bc trouvé sur http://phodd.net/gnu-bc/code/funcs.bc

1
Aethalides

Je cherche toujours une pure réponse bcpour savoir comment arrondir une seule valeur dans une fonction, mais voici une pure réponse bashname__:

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

Fondamentalement, ceci extrait soigneusement les décimales, multiplie le tout par 100 milliards (10¹⁰, 10**10 in bashname__), ajuste la précision et l’arrondi, effectue la division réelle, divise à nouveau la magnitude appropriée, puis réinsère la décimale.

Pas à pas:

La fonction embiggen() affecte la forme entière tronquée de son argument à $int et enregistre les nombres après le point dans $fraction. Le nombre de chiffres fractionnaires est noté dans $precision. Le calcul multiplie 10¹⁰ par la concaténation de $int et $fraction, puis ajuste cela pour correspondre à la précision (par exemple, embiggen 48.86 devient 10¹⁰ × 4886/100 et renvoie 488600000000, qui est 488,600,000,000).

Nous voulons une précision finale de centièmes. Nous multiplions donc le premier nombre par 100, en ajoutons 5 aux fins de l'arrondi, puis nous divisons le deuxième nombre. Cette assignation de $answer nous laisse cent fois la réponse finale.

Nous devons maintenant ajouter le point décimal. Nous attribuons une nouvelle valeur $int à $answer en excluant ses deux derniers chiffres, puis echoavec un point et le $answer en excluant la valeur $int déjà prise en charge. (Peu importe le bug de mise en évidence de la syntaxe qui le fait apparaître comme un commentaire)

(Bashism: l’exponentiation n’est pas POSIX, c’est donc un penchant. Une solution POSIX pure nécessiterait des boucles pour ajouter des zéros plutôt que d’utiliser des puissances de dix. En outre, "embiggen" est un mot parfaitement cromulant.)


L'une des principales raisons pour lesquelles j'utilise zshen tant que mon shell est qu'il prend en charge les mathématiques en virgule flottante. La solution à cette question est assez simple dans zshname__:

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

(J'adorerais voir quelqu'un ajouter un commentaire à cette réponse en essayant d'activer l'arithmétique en virgule flottante dans bashname__, mais je suis à peu près sûr qu'une telle fonctionnalité n'existe pas encore.)

1
Adam Katz

si vous avez le résultat, considérez par exemple 2.3747888

tout ce que tu dois faire est:

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

ceci arrondit correctement le nombre, par exemple:

(2.49999 + 0.5)/1 = 2.99999 

les décimales sont supprimées par bc et se réduit donc à 2 comme il se doit

1
user525061
#!/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..."

C'est en fait simple: il n'est pas nécessaire d'ajouter explicitement une variante "-0.5" codée en dur pour les nombres négatifs. Mathématiquement parlant, nous allons simplement calculer le valeur absolue de l'argument et toujours ajouter 0.5 comme nous le ferions normalement. Mais comme nous n’avons (malheureusement) pas de fonction abs() intégrée (sauf si nous en codons une), nous allons simplement nier l’argument s’il est négatif.

De plus, travailler avec le quotient en tant que paramètre ((pour ma solution, je dois pouvoir accéder séparément au dividende et au diviseur) s'est avéré très fastidieux). . C'est pourquoi mon script a un troisième paramètre supplémentaire.

1
syntaxerror

Voici une version abrégée de votre script, corrigée pour fournir le résultat souhaité:

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

Notez que arrondir au nombre entier le plus proche équivaut à ajouter 0,5 et prendre la parole, ou arrondir à la baisse (pour les nombres positifs).

En outre, le facteur d'échelle est appliqué au moment de l'opération; alors (ce sont des commandes bcname__, vous pouvez les coller dans votre terminal):

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
1
toddkaufmann
bc() {
        while :
        do
                while IFS='$\n' read i
                do
                        /usr/bin/bc <<< "scale=2; $i" | sed 's/^\./0&/'
                done
        done
}
0
sjas