Come arrotondare i decimali usando bc in bash?


45

Un breve esempio di ciò che voglio usando lo script 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

Supponendo che il prezzo sia: 48.86 La risposta sarà: 41.406779661 (41.40 in realtà perché sto usando scale=2;)

La mia domanda è: come arrotondare il secondo decimale per mostrare la risposta in questo modo ?: 41.41


Lo trovo strano perché "printf"% 0.2f \ n "41.445" ora funziona ma "printf"% 0.2f \ n "41.435 e printf"% 0.2f \ n "41.455" funzionano. Anche il tuo caso funziona (il 12.04) ma non con il .445
Luis Alvarado

8
IMHO, nessuno ha risposto a questa domanda in modo soddisfacente , forse perché bcnon è possibile ottenere ciò che viene richiesto (o almeno la domanda che mi ponevo quando ho trovato questo post), che è come arrotondare i decimali usandobc (che sembra essere chiamato da bash).
Adam Katz,

So che questa domanda è vecchia, ma non posso resistere a un commento che spinge la mia implementazione di bc: github.com/gavinhoward/bc . Per questo, è nella libreria integrata con la r()funzione, quindi puoi usarlo bc -le "r($float/1.18, 2)".
Gavin Howard

Risposte:


31

Una funzione rotonda bash:

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

Utilizzato nel tuo esempio di codice:

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

Buona fortuna: o)


1
tra l'altro, se il numero è negativo, dobbiamo usare-0.5
Aquarius Power il

Impossibile ottenere l'esempio per funzionare sulla console di test! "Debugging" ha rivelato un po 'che per esempio $2 = 3ho dovuto usare echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Pensa all'invito prima di stampare! Questo ti insegnerà di nuovo che è sempre importante capire cosa stai incollando da altrove.
syntaxerror,

@Aquarius Power grazie per l'ispirazione! Ora ho creato una versione dello script sopra che funzionerà con numeri sia negativi che positivi.
syntaxerror,

Questo è inutilmente complicato e fa tutti i tipi di aritmetica extra per arrivare alle risposte più semplici fornite da migas e zuberuber.
Adam Katz,

@AquariusPower +1 sì, è diverso per i numeri negativi. Per verificare un numero negativo (non intero!), Ho provato un po 'e ho optato per l' if [ eco "$ 1/1" | bc` -gt 0] `- c'è un modo più elegante per controllare (tranne analizzare la stringa per" - ")?
RobertG

30

La soluzione più semplice:

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

2
Come notato anche in un commento sopra ( askubuntu.com/a/179949/512213 ), questo purtroppo si comporterebbe in modo errato per i numeri negativi.
RobertG

2
@RobertG: Perché? Questa soluzione non usa +0.5. Prova printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"e otterrai 41.41e -41.41.
musiphil,

1
È anche possibile utilizzare una pipa:echo "$float/1.18" | bc -l | xargs printf %.2f
dessert

La soluzione in questa risposta mi dà:, a bash: printf: 1.69491525423728813559: invalid numberseconda delle impostazioni locali.
Torsten Bronger,

19

Arrotondamento Bash / Awk:

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

Se hai Python puoi usare qualcosa del genere:

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

Grazie per il suggerimento ma non risolve ciò di cui ho bisogno. Devo farlo solo usando bash ...
blackedx

1
Il comando python è più leggibile e buono per gli script rapidi. Supporta anche arrotondamenti di cifre arbitrari aggiungendo ad esempio ", 3" alla funzione di arrotondamento. Grazie
Elle il

echo "$float" |awk '{printf "%.2f", $1/1.18}'eseguirà la matematica richiesta della domanda alla percentuale richiesta di centesimi. Questo è tanto "usare bash" quanto la bcchiamata nella domanda.
Adam Katz,

2
Per Python puoi semplicemente usare qualcosa di simile a python -c "print(round($num))"dove num=4.678923. Non c'è bisogno di scherzare con Stdin. È possibile anche turno di n cifre in questo modo: python -c "print(round($num, $n))".
Sei,

Sono riuscito a fare qualcosa di abbastanza pulito con quella soluzione, il mio problema era 0,5, in questo caso non ho sempre ottenuto il round di cui avevo bisogno, quindi ho pensato a: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Quando + arrotonderà per eccesso e - arrotonderà per difetto).
Yaron,

4

Ecco una soluzione puramente bc. Regole di arrotondamento: a +/- 0,5, arrotondamento da zero.

Inserisci la scala che stai cercando in $ result_scale; la tua matematica dovrebbe essere dove $ MATH si trova nell'elenco dei comandi 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

1
molto interessante! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 come spiegato qui :)
Aquarius Power

Lo nomina "migliore risposta".
Marc Tamsky, il

2

So che è una vecchia domanda, ma ho una pura soluzione "bc" senza "if" o rami:

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

Usalo come bcr '2/3' 5o bcr '0.666666' 2-> (espressione seguita da scala)

Questo è possibile perché in bc (come C / C ++) è permesso mescolare espressioni logiche nei tuoi calcoli. L'espressione ((t>0)-(t<0))/2)valuterà a +/- 0,5 a seconda del segno di 't' e quindi utilizzerà il valore corretto per l'arrotondamento.


Sembra pulito, ma bcr "5 / 2" 0restituisce 2invece di 3. Mi sto perdendo qualcosa?
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..."

In realtà è semplice: non è necessario aggiungere esplicitamente una variante "-0.5" codificata per i numeri negativi. In termini matematici, calcoleremo semplicemente il valore assoluto dell'argomento e aggiungeremo 0,5 come normalmente faremmo. Ma poiché (purtroppo) non abbiamo una abs()funzione integrata a nostra disposizione (a meno che non ne codifichiamo una), negheremo semplicemente l'argomento se è negativo.

Inoltre, è stato molto complicato lavorare con il quoziente come parametro (poiché per la mia soluzione, devo essere in grado di accedere al dividendo e al divisore separatamente). Questo è il motivo per cui il mio script ha un terzo parametro aggiuntivo.


@muru sulle tue recenti modifiche: herestrings invece di echo .. |, shell arithmetic, a che cosa serve echo $()? Questo è facile da spiegare: le tue stringhe qui richiederanno sempre una directory scrivibile/tmp ! E la mia soluzione funziona anche in un ambiente di sola lettura, ad esempio una shell di root di emergenza in cui /è non è sempre scrivibile per impostazione predefinita. Quindi c'era una buona ragione per cui l'ho codificato in quel modo.
syntaxerror,

Herestrings, sono d'accordo. Ma quando echo $()mai è necessario? Questo e il rientro mi hanno spinto a modificare, le eruzioni sono appena avvenute.
muru,

@muru Beh, il caso più inutile echo $()che era in ritardo per essere risolto era in realtà la penultima riga, lol. Grazie per il testa a testa. Almeno questo sembra molto meglio ora, non posso negare. Ad ogni modo, ho amato la logica in questo. Un sacco di roba booleana, meno "se" s superfluo (che sicuramente farebbe saltare il codice del 50 percento).
syntaxerror,

1

Sto ancora cercando una bcrisposta pura a come arrotondare un solo valore all'interno di una funzione, ma ecco una bashrisposta pura :

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

Fondamentalmente, questo estrae attentamente i decimali, moltiplica tutto per 100 miliardi (10¹⁰, 10**10in bash), regola la precisione e l'arrotondamento, esegue la divisione effettiva, divide nuovamente alla grandezza appropriata e quindi reinserisce il decimale.

Passo dopo passo:

La embiggen()funzione assegna la forma intera troncata del suo argomento $inte salva i numeri dopo il punto in $fraction. Il numero di cifre frazionarie è indicato in $precision. La matematica moltiplica 10¹⁰ per la concatenazione di $inte $fractionquindi la regola per adattarla alla precisione (ad esempio embiggen 48.86diventa 10¹⁰ × 4886/100 e restituisce 488600000000488.600.000.000).

Vogliamo una precisione finale di centesimi, quindi moltiplichiamo il primo numero per 100, aggiungiamo 5 per arrotondare e quindi dividiamo il secondo numero. Questo incarico $answerci lascia cento volte la risposta finale.

Ora dobbiamo aggiungere il punto decimale. Assegniamo un nuovo $intvalore per $answerescludere le sue ultime due cifre, quindi echocon un punto e $answerescludendo il $intvalore già curato. (Non importa il bug di evidenziazione della sintassi che fa apparire questo come un commento)

(Bashismo: l'esponenziazione non è POSIX, quindi questo è un bashismo. Una soluzione POSIX pura richiederebbe anelli per aggiungere zeri piuttosto che usare poteri di dieci. Inoltre, "embiggen" è una parola perfettamente cromulante.)


Uno dei motivi principali che uso zshcome shell è che supporta la matematica in virgola mobile. La soluzione a questa domanda è piuttosto semplice in zsh:

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

(Mi piacerebbe vedere qualcuno aggiungere un commento a questa risposta con il trucco di abilitare l'aritmetica in virgola mobile bash, ma sono abbastanza sicuro che una tale funzionalità non esiste ancora.)


1

se hai il risultato, ad esempio considera 2.3747888

tutto quello che devi fare è:

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

questo arrotonda correttamente il numero esempio:

(2.49999 + 0.5)/1 = 2.99999 

i decimali vengono rimossi da bce quindi arrotondano per difetto a 2 come dovrebbe


1

Ho dovuto calcolare la durata totale di una raccolta di file audio.

Quindi ho dovuto:

A. ottenere la durata per ciascun file ( non mostrato )

B. sommare tutte le durate (erano ciascuna in NNN.NNNNNN(fp) secondi)

C. ore, minuti, secondi, secondi separati.

D. emette una stringa di HR: MIN: SEC: FRAMES, dove frame = 1/75 sec.

(I frame provengono dal codice SMPTE utilizzato negli studi.)


A: usa ffprobee analizza la riga della durata in un numero fp (non mostrato)

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"

1

Ecco una versione abbreviata del tuo script, corretta per fornire l'output desiderato:

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

Si noti che arrotondare per eccesso al numero intero più vicino equivale ad aggiungere 0,5 e prendere il piano o arrotondare per difetto (per i numeri positivi).

Inoltre, il fattore di scala viene applicato al momento dell'operazione; quindi (questi sono bccomandi, puoi incollarli nel tuo terminale):

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

Implementazione pura in bc come richiesto

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

se lo metti in un file chiamato Functions.bc, puoi arrotondare con

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

Codice per l'implementazione di bc disponibile su http://phodd.net/gnu-bc/code/funcs.bc


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

0

Puoi usare awk, non è necessario alcun pipe:

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

Impostare preventivamente il prezzo con la variabile d'ambiente PRICE=<val>.

  • Quindi chiama awk: $PRICEentrerà in awk come variabile awk price.

  • Viene quindi arrotondato al centesimo più vicino con +,005.

  • L'opzione di formattazione di printf %.2flimita la scala a due cifre decimali.

Se devi farlo molto in questo modo è molto più efficiente della risposta selezionata:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.