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


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)


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)

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()))"

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

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.


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.


@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.)


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


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"

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

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


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.