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

45

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 parce que je l'utilise scale=2;)

Ma question est la suivante: comment arrondir la deuxième décimale pour afficher la réponse de cette manière ?: 41.41

Blackedx
la source
Je trouve cela étrange car "printf"% 0.2f \ n "41.445" fonctionne à présent, mais "printf"% 0.2f \ n "41.435 et printf"% 0.2f \ n "41.455" fonctionnent. Même votre propre cas fonctionne (sur 12.04) mais pas avec le .445
Luis Alvarado
8
IMHO, personne n'a répondu à cette question de manière satisfaisante , peut-être parce bcque je ne peux pas atteindre ce qui est demandé (ou du moins la question que je posais quand j'ai trouvé ce post), qui est comment arrondir les décimales en utilisantbc (qui est appelé par bash).
Adam Katz
Je sais que cette question est ancienne, mais je ne peux pas résister à un commentaire poussant ma propre implémentation de bc: github.com/gavinhoward/bc . Pour cela, il se trouve dans la bibliothèque intégrée avec la r()fonction, vous pouvez donc l'utiliser bc -le "r($float/1.18, 2)".
Gavin Howard

Réponses:

31

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)

utilisateur85321
la source
1
Par ailleurs, si le nombre est négatif, nous devons utiliser-0.5
Aquarius Power
Impossible d'obtenir l'exemple pour travailler sur la console de test! "Débogage" un peu révélé que par exemple $2 = 3je devais utiliser echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Attention à l' environnement avant printf! Cela vous apprendra qu'il est toujours important de comprendre ce que vous copiez depuis ailleurs.
erreur de syntaxe
@Aquarius Power merci pour l'inspiration! J'ai maintenant créé une version du script ci-dessus qui fonctionnera avec les nombres négatifs et positifs.
erreur de syntaxe
Ceci est inutilement compliqué et fait toutes sortes d'arithmétiques supplémentaires pour arriver aux réponses plus simples fournies par migas et zuberuber.
Adam Katz
@AquariusPower +1 Oui, c'est différent pour les nombres négatifs. Pour tester un nombre négatif (non entier!), J'ai essayé un peu et j'ai opté pour l' if [ écho "$ 1/1" | bc` -gt 0] `- existe-t-il un moyen plus élégant de vérifier (à l'exception de l'analyse de la chaîne de caractères pour" - ")?
RobertG
30

La solution la plus simple:

printf %.2f $(echo "$float/1.18" | bc -l)
migas
la source
2
Comme indiqué également dans un commentaire ci-dessus ( askubuntu.com/a/179949/512213 ), cela se comporterait de manière incorrecte pour les nombres négatifs, malheureusement.
RobertG
2
@RobertG: Pourquoi? Cette solution ne utilise pas +0.5. Essayez printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"et vous obtiendrez 41.41et -41.41.
musiphil
1
Egalement possible avec une pipe:echo "$float/1.18" | bc -l | xargs printf %.2f
dessert
La solution dans cette réponse me donne:, bash: printf: 1.69491525423728813559: invalid numberselon les paramètres régionaux.
Torsten Bronger
19

Bash / awk arrondi:

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

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

echo "4.678923" | python -c "print round(float(raw_input()))"
zuberuber
la source
Merci pour le tuyau mais cela ne résout pas ce dont j'ai besoin. Je dois le faire simplement en utilisant bash ...
blackedx
1
La commande python est plus lisible et convient aux scripts rapides. Prend également en charge l’arrondissement arbitraire des chiffres en ajoutant, par exemple, ", 3" à la fonction arrondie. Merci
Elle
echo "$float" |awk '{printf "%.2f", $1/1.18}'effectuera le calcul demandé de la question à la percision de centièmes demandée. C'est autant "utiliser" que l' bcappel dans la question.
Adam Katz
2
Pour Python, vous pouvez simplement utiliser quelque chose comme python -c "print(round($num))"num=4.678923. Il n'y a pas besoin de perdre son temps avec stdin. Vous pouvez également arrondir à n chiffres comme ceci: python -c "print(round($num, $n))".
Six
J'ai réussi à faire quelque chose d'assez propre avec cette solution, mon problème était de 0,5; dans ce cas, je n'avais pas toujours le tour dont j'avais besoin, alors j'ai proposé ce qui suit: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Quand + arrondira et arrondira à la baisse).
Yaron
4

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
MetroEast
la source
1
très intéressant! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 comme expliqué ici :)
Aquarius Power
Je propose cela comme "meilleure réponse".
Marc Tamsky le
2

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' 5ou 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.

Marcus Kolenda
la source
Cela a l'air bien, mais bcr "5 / 2" 0retourne 2au lieu de 3. Est-ce que je manque quelque chose?
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..."

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 la valeur absolue de l'argument et ajouter toujours 0,5 comme nous le ferions normalement. Mais comme nous n’avons (malheureusement) pas de abs()fonction intégrée à notre disposition (sauf si nous en codons une), nous allons simplement nier l’argument s’il est négatif.

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

erreur de syntaxe
la source
@muru à propos de vos modifications récentes: herestrings à la place de echo .. |, shell arithmetic, à quoi ça sert echo $()? C'est facile à expliquer: vos chaînes ici nécessiteront toujours un répertoire accessible en écriture/tmp ! Et ma solution fonctionnera également sur un environnement en lecture seule, par exemple un shell racine d'urgence dans lequel l' écriture /n'est pas toujours accessible en écriture par défaut. Il y avait donc une bonne raison pour laquelle je l'ai codé de cette façon.
erreur de syntaxe
Herestrings, je suis d'accord. Mais quand est-il echo $()nécessaire? Cela et l'indentation ont incité mes modifications, les herestrings viennent juste d'arriver.
muru
@muru Eh bien, le cas le plus inutile de echo $()cela était trop tard pour être réparé était en fait l'avant dernière ligne, lol. Merci pour l'information. Au moins celui-ci a l'air beaucoup mieux maintenant, je ne peux pas le nier. Quoi qu'il en soit, j'ai adoré la logique en cela. Beaucoup de choses booléennes, moins de "if" s (qui feraient sûrement exploser le code de 50%).
erreur de syntaxe
1

Je cherche toujours une bcréponse pure à la méthode pour arrondir une seule valeur dans une fonction, mais voici une bashréponse pure :

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

En gros, cela extrait soigneusement les décimales, multiplie tout par 100 milliards (10¹⁰, 10**10en bash), ajuste de précision et de l' arrondissement, effectue la division réelle, divise Retour à la grandeur appropriée, et réinsère alors la décimale.

Pas à pas:

La embiggen()fonction affecte la forme entière tronquée de son argument à $intet 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 $intet $fractionpuis ajuste cela pour correspondre à la précision ( embiggen 48.86devient par exemple 10¹⁰ × 4886/100 et renvoie 488600000000ce 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 cession de $answernous laisse cent fois la réponse finale.

Nous devons maintenant ajouter le point décimal. Nous attribuons une nouvelle $intvaleur pour $answerexclure ses deux derniers chiffres, puis echoun point et l’ $answerexclusion de la $intvaleur 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 zshle shell est qu'il prend en charge les mathématiques en virgule flottante. La solution à cette question est assez simple dans zsh:

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

(J'aimerais beaucoup que quelqu'un ajoute un commentaire à cette réponse en essayant d'activer l'arithmétique en virgule flottante bash, mais je suis à peu près sûr qu'une telle fonctionnalité n'existe pas encore.)

Adam Katz
la source
1

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 bcet donc il arrondit à 2 comme il se doit

utilisateur525061
la source
1

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 montré )

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: utiliser ffprobeet analyser la ligne de durée dans un nombre fp (non montré)

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

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 bccommandes, 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
Toddkaufmann
la source
1

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

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

Vous pouvez utiliser awk, aucun tuyau nécessaire:

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

Définissez le prix au préalable avec la variable d'environnement PRICE=<val>.

  • Ensuite, appelez awk - $PRICEentrera dans awk en tant que variable awk price.

  • Il est ensuite arrondi au 100ème le plus proche avec +.005.

  • L'option de formatage printf %.2flimite l'échelle à deux décimales.

Si vous devez faire cela beaucoup de cette façon, c'est beaucoup plus efficace que la réponse choisie:

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
la source