Jak zaokrąglać dziesiętne za pomocą bc w bash?

45

Szybki przykład tego, czego chcę za pomocą skryptów 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

Zakładając, że cena wynosi: 48,86 Odpowiedź brzmi: 41.406779661 (41,40 właściwie ponieważ używam scale=2;)

Moje pytanie brzmi: jak zaokrąglić drugi przecinek, aby pokazać odpowiedź w ten sposób ?: 41,41

blackedx
źródło
Uważam to za dziwne, ponieważ „printf”% 0.2f \ n "41.445” teraz działa, ale „printf”% 0.2f \ n "41,435 i printf"% 0,2f \ n "41,455" działają. Nawet Twoja własna obudowa działa (w dniu 12.04), ale nie działa z .445
Luis Alvarado
8
IMHO, nikt nie odpowiedział na to pytanie w zadowalający sposób , być może dlatego, że bcnie jest w stanie osiągnąć tego, o co się prosi (a przynajmniej pytania, które zadałem, kiedy znalazłem ten post), czyli jak zaokrąglać ułamki dziesiętne za pomocąbc (to się nazywa bash).
Adam Katz
Wiem, że to pytanie jest stare, ale nie mogę się oprzeć komentarzowi pchającemu własną implementację bc: github.com/gavinhoward/bc . W tym celu znajduje się we wbudowanej bibliotece z r()funkcją, dzięki czemu można jej używać bc -le "r($float/1.18, 2)".
Gavin Howard

Odpowiedzi:

31

Funkcja bash round:

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

Używane w twoim przykładzie kodu:

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

Powodzenia: o)

użytkownik85321
źródło
1
btw, jeśli liczba jest ujemna, musimy użyć-0.5
Aquarius Power
Nie udało się uzyskać przykładu do pracy na konsoli testowej! „Debugowanie” trochę ujawniło, że np. $2 = 3Musiałem użyć echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Pamiętaj o env przed printf! To nauczy Cię ponownie, że zawsze ważne jest, aby zrozumieć, co kopiujesz z innego miejsca.
składniaerror
@Aquarius Power dzięki za inspirację! Teraz rozwidliłem wersję powyższego skryptu, która będzie działać zarówno z liczbami ujemnymi, jak i dodatnimi.
syntaxerror
Jest to niepotrzebnie skomplikowane i wymaga wszelkiego rodzaju dodatkowej arytmetyki, aby uzyskać prostsze odpowiedzi udzielone przez migas i zuberuber.
Adam Katz
@AquariusPower +1 tak, jest inaczej dla liczb ujemnych. Aby sprawdzić liczbę ujemną (niecałkowitą!), Spróbowałem trochę i zdecydowałem się na if [ echo „$ 1/1” | bc` -gt 0] `- czy istnieje bardziej elegancki sposób sprawdzania (oprócz parsowania ciągu dla„ - ”)?
RobertG
30

Najprostsze rozwiązanie:

printf %.2f $(echo "$float/1.18" | bc -l)
migas
źródło
2
Jak zauważono również w powyższym komentarzu ( askubuntu.com/a/179949/512213 ), niestety zachowałoby się to nieprawidłowo w przypadku liczb ujemnych.
RobertG
2
@RobertG: Dlaczego? To rozwiązanie nie wykorzystuje +0.5. Spróbuj, printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"a dostaniesz 41.41i -41.41.
musiphil
1
Możliwe również za pomocą fajki:echo "$float/1.18" | bc -l | xargs printf %.2f
deser
Rozwiązanie w tej odpowiedzi daje mi: bash: printf: 1.69491525423728813559: invalid numberw zależności od ustawień regionalnych.
Torsten Bronger
19

Zaokrąglanie Bash / awk:

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

Jeśli masz python, możesz użyć czegoś takiego:

echo "4.678923" | python -c "print round(float(raw_input()))"
zuberuber
źródło
Dzięki za wskazówkę, ale to nie rozwiązuje problemu. Muszę to zrobić tylko za pomocą bash ...
blackedx
1
Polecenie python jest bardziej czytelne i nadaje się do szybkich skryptów. Obsługuje również dowolne zaokrąglanie cyfr, dodając np. „, 3” do funkcji zaokrąglania. Dzięki
Elle,
echo "$float" |awk '{printf "%.2f", $1/1.18}'wykona zadaną matematykę pytania z żądaną dokładnością setnych części. To tyle samo „używania bash”, co bcwywołanie w pytaniu.
Adam Katz
2
W Pythonie możesz po prostu użyć czegoś takiego jak python -c "print(round($num))"gdzie num=4.678923. Nie ma potrzeby, aby grzebać w standardzie. Można również okrągły do n cyfr tak: python -c "print(round($num, $n))".
Sześć
Udało mi się zrobić coś całkiem fajnego z tym rozwiązaniem, mój problem wynosił 0,5, w tym przypadku nie zawsze dostałem potrzebną rundę, więc wymyśliłem następujące: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Kiedy + zaokrągli w górę i - zaokrągli w dół).
Yaron
4

Oto rozwiązanie wyłącznie BC. Zasady zaokrąglania: przy +/- 0,5, zaokrąglić od zera.

Umieść skalę, której szukasz, w $ result_scale; matematyka powinna znajdować się w miejscu, w którym $ MATH znajduje się na liście poleceń 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
źródło
1
bardzo interesujące! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 jak wyjaśniono tutaj :)
Aquarius Power
Nominuję to za „najlepszą odpowiedź”.
Marc Tamsky
2

Wiem, że to stare pytanie, ale mam czyste rozwiązanie „bc” bez „if” lub rozgałęzień:

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

Użyj go jak bcr '2/3' 5lub bcr '0.666666' 2-> (wyrażenie poprzedzone skalą)

Jest to możliwe, ponieważ w bc (jak C / C ++) dozwolone jest mieszanie wyrażeń logicznych w twoich obliczeniach. Wyrażenie ((t>0)-(t<0))/2)będzie miało wartość +/- 0,5 w zależności od znaku „t”, a zatem zastosuje odpowiednią wartość do zaokrąglenia.

Marcus Kolenda
źródło
Wygląda to ładnie, ale bcr "5 / 2" 0zwraca 2zamiast 3. Czy coś brakuje?
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..."

To jest naprawdę proste: nie ma potrzeby jawnego dodawania zakodowanego wariantu „-0,5” dla liczb ujemnych. Mówiąc matematycznie, po prostu obliczymy wartość bezwzględną argumentu i nadal dodamy 0,5, jak zwykle. Ale ponieważ (niestety) nie mamy do dyspozycji żadnej wbudowanej abs()funkcji (chyba że ją kodujemy), po prostu zanegujemy argument, jeśli jest on ujemny.

Poza tym praca z ilorazem jako parametrem okazała się bardzo kłopotliwa (ponieważ dla mojego rozwiązania muszę mieć dostęp do dywidendy i dzielnika osobno). Dlatego mój skrypt ma dodatkowy trzeci parametr.

błąd składni
źródło
@muru o twoich ostatnich edycjach: zamiast instrukcji echo .. |, arytmetyki powłoki, jaki jest sens echo $()? Łatwo to wytłumaczyć: ciągi tutaj będą zawsze wymagały zapisu/tmp katalogu! I moje rozwiązanie będzie również pracować w środowisku tylko do odczytu, np rootem awaryjnego gdzie /jest nie zawsze zapisu domyślnie. Był więc dobry powód, dla którego kodowałem to w ten sposób.
składnia błąd
Zgadzam się. Ale kiedy jest to echo $()kiedykolwiek potrzebne? To i wcięcie skłoniło mnie do edycji, właśnie tu się pojawiły.
muru
@muru Cóż, najbardziej bezcelowym przypadkiem, echo $()który został naprawiony, była przedostatnia linia, lol. Dzięki za zgłoszenie się. Przynajmniej ten wygląda teraz znacznie lepiej, nie mogę zaprzeczyć. W każdym razie podobała mi się logika. Dużo boolowskich rzeczy, mniej zbędnych „jeśli” (co z pewnością wysadziłoby kod o 50 procent).
składnia błąd
1

Wciąż szukam czystej bcodpowiedzi na to, jak zaokrąglić tylko jedną wartość w funkcji, ale oto czysta bashodpowiedź:

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

Zasadniczo, to ostrożnie wyodrębnia ułamki dziesiętne, mnoży wszystko przez 100 miliardów (10¹⁰, 10**10in bash), dostosowuje precyzję i zaokrąglanie, dokonuje rzeczywistego podziału, dzieli z powrotem do odpowiedniej wielkości, a następnie ponownie wprowadza ułamek dziesiętny.

Krok po kroku:

W embiggen()wyznacza funkcyjne ściętego całkowitą postaci jej argumentu $inti zapisuje numery po kropką $fraction. Liczba cyfr ułamkowych jest zapisana w $precision. Mnoży matematyczne 10¹⁰ przez złączenie $inti $fraction, a następnie dostosowuje się, że w celu dopasowania do precyzji (np embiggen 48.86się 10¹⁰ x 4886/100 i wraca 488600000000który jest 488600000000).

Chcemy ostatecznej precyzji setnych, więc mnożymy pierwszą liczbę przez 100, dodajemy 5 dla celów zaokrąglania, a następnie dzielimy drugą liczbę. To zadanie $answerpozostawia nas sto razy ostateczną odpowiedź.

Teraz musimy dodać przecinek dziesiętny. Przypisujemy nową $intwartość $answerwykluczeniu ostatnich dwóch cyfr, następnie echokropką i $answerwykluczeniu $intwartości, o którą już zadbano. (Nie wspominając o błędzie podświetlania składni, który sprawia, że ​​wygląda to jak komentarz)

(Bashism: potęgowanie nie jest POSIX, więc jest to bashism. Czyste rozwiązanie POSIX wymagałoby, aby pętle dodawały zera, a nie używały potęg dziesięciu. Ponadto „embiggen” to słowo doskonale cromulant.)


Jednym z głównych powodów, dla których używam zshmojej powłoki jest to, że obsługuje matematykę zmiennoprzecinkową. Rozwiązanie tego pytania jest dość proste w zsh:

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

(Chciałbym zobaczyć, jak ktoś dodaje komentarz do tej odpowiedzi, podając sztuczkę włączenia arytmetyki zmiennoprzecinkowej bash, ale jestem pewien, że taka funkcja jeszcze nie istnieje.)

Adam Katz
źródło
1

jeśli masz wynik, na przykład rozważ 2.3747888

wszystko co musisz zrobić to:

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

to poprawnie zaokrągla liczbę, przykład:

(2.49999 + 0.5)/1 = 2.99999 

ułamki dziesiętne są usuwane przez, bcwięc zaokrągla w dół do 2, tak jak powinno

użytkownik525061
źródło
1

Musiałem obliczyć całkowity czas trwania zbioru plików audio.

Musiałem więc:

A. uzyskaj czas trwania każdego pliku ( nie pokazano )

B. zsumuj wszystkie czasy trwania (wszystkie były w NNN.NNNNNN(fp) sekundach)

C. oddzielne godziny, minuty, sekundy, sekundy.

D. wyprowadzić ciąg HR: MIN: SEK: ZDJĘCIA, gdzie ramka = 1/75 sek.

(Ramki pochodzą z kodu SMPTE używanego w studiach).


Odp .: użyj ffprobei parsuj czas trwania linii na numer fp (nie pokazano)

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 

DO:

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

RE:

#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
źródło
1

Oto skrócona wersja skryptu, naprawiona w celu zapewnienia pożądanych wyników:

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

Zauważ, że zaokrąglenie w górę do najbliższej liczby całkowitej jest równoważne dodaniu .5 i zabraniu głosu lub zaokrągleniu w dół (dla liczb dodatnich).

Ponadto współczynnik skali jest stosowany w momencie operacji; więc (są to bcpolecenia, możesz je wkleić do terminala):

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
źródło
1

Implementacja czystego bc zgodnie z żądaniem

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

jeśli umieścisz to w pliku o nazwie functions.bc, możesz zaokrąglić w górę za pomocą

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

Kod implementacji bc można znaleźć na stronie http://phodd.net/gnu-bc/code/funcs.bc

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

Możesz użyć awk, bez rur:

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

Ustaw wcześniej cenę za pomocą zmiennej środowiskowej PRICE=<val>.

  • Następnie wywołaj awk - $PRICEprzejdzie do awk jako zmienna awk price.

  • Następnie jest zaokrąglany w górę do najbliższej setnej z +.005.

  • Opcja formatowania printf %.2fogranicza skalę do dwóch miejsc po przecinku.

Jeśli musisz to robić dużo w ten sposób, jest to o wiele bardziej wydajne niż wybrana odpowiedź:

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
źródło