Jak korzystać z podziału zmiennoprzecinkowego w bash?


242

Próbuję podzielić dwie szerokości obrazu w skrypcie Bash, ale bash daje mi 0w rezultacie:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Przestudiowałem przewodnik po Bash i wiem, że powinienem go użyć bc we wszystkich przykładach w Internecie, których używają bc. W echostarałem się umieścić tę samą rzecz w moim SCALE, ale to nie działa.

Oto przykład, który znalazłem w tutorialach:

echo "scale=2; ${userinput}" | bc 

Jak mogę sprawić, żeby Bash dał mi pływaka 0.5?


9
Komentarz dla wszystkich, którzy próbują wykonywać arytmetykę zmiennoprzecinkową w twoim skrypcie, zadaj sobie pytanie: czy naprawdę potrzebuję arytmetyki zmiennoprzecinkowej? czasem naprawdę można się bez tego dogadać. Zobacz na przykład ostatnią część BashFAQ / 022 .
gniourf_gniourf

Odpowiedzi:


242

Nie możesz bash robi tylko liczby całkowite; Państwo musi przekazać narzędzia takiego jak bc.


8
jak mogę delegować narzędzie takie jak bc, aby umieścić odpowiedź w zmiennej RESULT?
Medya Gh,

2
więc masz na myśli VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh

54
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)lub VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")bez $ (()) (podwójne nawiasy); który jest rozszerzany przez bash przed wykonaniem polecenia
Nahuel Fouilleul

4
To prawda, ale awk jest zwykle bardziej prawdopodobne, że zostanie już zainstalowany w systemie.
CMCDragonkai

9
@NahuelFouilleul Masz tutaj najlepszą odpowiedź. Powinna być naprawdę własną odpowiedzią i zaakceptowana jako odpowiedź. Ta konkretna linia była niezwykle przydatna: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David

197

możesz to zrobić:

bc <<< 'scale=2; 100/3'
33.33

AKTUALIZACJA 20130926 : możesz użyć:

bc -l <<< '100/3' # saves a few hits

8
... i dodaje wiele cyfr. Przynajmniej na mojej maszynie to 33.33333333333333333333, podczas gdy poprzednia daje 33.33.
Andreas Spindler,

12
@AndreasSpindler Rodzaj starego postu, ale jeśli ktoś chciałby wiedzieć, można to zmienić, stosując polecenie skali np. bc -l <<< 'scale=2; 100/3'
Martie

Tylko uważaj, jeśli chcesz uzyskać liczbę całkowitą do późniejszego użycia w bashu, używając skali = 0. Począwszy od wersji 1.06.95, bc z jakiegoś powodu ignoruje zmienną skalową, gdy liczby wejściowe mają część dziesiętną. Może to jest w dokumentacji, ale nie mogłem tego znaleźć. Spróbuj: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell

1
@GregBell Strona podręcznika mówi Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.I jest dodatkowa uwaga dla /operatora:The scale of the result is the value of the variable scale.
psmith

2
Dzięki @psmith. Co ciekawe, ponieważ / mówi „skala wyniku jest wartością skali zmiennej”, ale nie w przypadku mnożenia. Moje lepsze przykłady: bc <<< 'scale=1; 1*3.00001' skala jest naprawdę 5 z jakiegoś powodu, bc <<< 'scale=1; 1/3.000001' skala wynosi 1. Co ciekawe, dzielenie przez 1 ustawia to prosto: bc <<< 'scale=1; 1*3.00001/1'skala to 1
Greg Bell

136

grzmotnąć

Jak zauważyli inni, bashnie obsługuje arytmetyki zmiennoprzecinkowej, chociaż można ją sfałszować za pomocą pewnych stałych sztuczek dziesiętnych, np. Z dwoma miejscami po przecinku:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Wynik:

.33

Zobacz odpowiedź Nilfreda na podobne, ale bardziej zwięzłe podejście.

Alternatywy

Oprócz wspomnianych bci awkalternatywnych istnieją również:

clisp

clisp -x '(/ 1.0 3)'

z oczyszczoną mocą wyjściową:

clisp --quiet -x '(/ 1.0 3)'

lub poprzez stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

genialny kalkulator cli

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

gnuplot

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

Lub:

jq -n 1/3

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

lub poprzez stdin:

echo 'print(1/3)' | lua

maksima

echo '1/3,numer;' | maxima

z oczyszczoną mocą wyjściową:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

węzeł

echo 1/3 | node -p

oktawa

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

z oczyszczoną mocą wyjściową:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

rubin

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Z oczyszczoną mocą wyjściową:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

echo 'print $(( 1/3. ))' | zsh

jednostki

units 1/3

Z kompaktową mocą wyjściową:

units --co 1/3

Innych źródeł

Stéphane Chazelas odpowiedział na podobne pytanie w Unix.SX.


9
Świetna odpowiedź. Wiem, że został opublikowany kilka lat po pytaniu, ale bardziej zasługuje na odpowiedź.
Brian Cline

2
Jeśli masz dostępne zsh, warto rozważyć napisanie skryptu w zsh zamiast Bash
Andrea Corbellini

Kciuk w górę dla gnuplot :)
andywiecko

Ładna lista. Zwłaszcza echo 1/3 | node -pjest krótki.
Johnny Wong,

39

Poprawiając nieco odpowiedź marvina:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc nie zawsze jest dostarczany jako zainstalowany pakiet.


4
Skrypt awk musi exituniemożliwić odczyt ze strumienia wejściowego. Sugeruję również użycie -vflag awk, aby zapobiec zespołowi pochylonej wykałaczki. Więc:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley

2
Bardziej awkish sposobem na to byłoby przeczytać argumenty ze strumienia wejściowego: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bcjest częścią POSIX, zwykle jest wstępnie zainstalowany.
fuz

to zadziałało dla mnie przy użyciu git bash w Windows 7 ... dzięki :)
The Beast


28

Jako alternatywę dla bc możesz użyć awk w swoim skrypcie.

Na przykład:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

Powyżej „% .2f” mówi funkcji printf, aby zwracała liczbę zmiennoprzecinkową z dwiema cyframi po miejscu dziesiętnym. Użyłem echa do potokowania zmiennych jako pól, ponieważ awk działa na nich poprawnie. „$ 1” i „$ 2” odnoszą się do pierwszego i drugiego pola wprowadzonego do awk.

Możesz zapisać wynik jako inną zmienną, używając:

RESULT = `echo ...`

Doskonały! Dzięki. Jest to pomocne w środowisku osadzonym, w którym nie występuje bc. Zaoszczędziłeś mi trochę czasu na kompilację.
entuzjastyczny

19

To idealny czas, aby wypróbować Zsh, (prawie) superset Bash, z wieloma dodatkowymi funkcjami, w tym matematyką zmiennoprzecinkową. Oto twój przykład w Zsh:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Ten post może ci pomóc: bash - Warto przejść na zsh do codziennego użytku?


3
Jestem wielkim fanem Zsh i używam go przez ostatnie 4 lata, ale interaktywne użycie jest tutaj dobrym akcentem. Skrypt, który wymaga zsh, zwykle nie będzie zbyt przenośny na różnorodnym zestawie maszyn, ponieważ zazwyczaj nie jest standardowy, niestety (szczerze mówiąc, może to w porządku; OP nie powiedział dokładnie, jak będzie używany).
Brian Cline,

17

Cóż, zanim liczba zmiennoprzecinkowa była okresem, w którym stosowano stałą logikę dziesiętną:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

Ostatnia linia to bashim, jeśli nie używasz bash, wypróbuj ten kod:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

Uzasadnieniem tego kodu jest: pomnóż przez 100 przed dzieleniem, aby uzyskać 2 miejsca po przecinku.


5

Jeśli znalazłeś wariant preferencji, możesz również zawinąć go w funkcję.

Tutaj zawijam trochę bashizmu w funkcję div:

Jedna wkładka:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

Lub wiele linii:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Teraz masz funkcję

div <dividend> <divisor> [<precision=2>]

i używaj go jak

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

AKTUALIZACJA Dodałem mały skrypt, który zapewnia podstawowe operacje z liczbami zmiennoprzecinkowymi dla bash:

Stosowanie:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

A tutaj skrypt:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}

1
local _d=${3:-2}jest prostsze
KamilCuk

4

To naprawdę nie jest zmiennoprzecinkowe, ale jeśli chcesz czegoś, co ustawia więcej niż jeden wynik w jednym wywołaniu bc ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

oblicza powierzchnię i średnicę koła, którego promień podano w 1 $


4

Istnieją scenariusze, w których nie można używać bc, ponieważ może po prostu nie być obecny, jak w niektórych odciętych wersjach busyboksa lub systemach wbudowanych. W każdym razie dobrze jest ograniczyć zewnętrzne zależności, dlatego zawsze możesz dodawać zera do liczby dzielonej przez (licznik), to znaczy tyle samo, co mnożenie przez potęgę 10 (powinieneś wybrać potęgę 10 zgodnie z potrzebna precyzja), dzięki czemu wynik dzielenia będzie liczbą całkowitą. Gdy masz już tę liczbę całkowitą, potraktuj ją jako ciąg znaków i ustaw kropkę dziesiętną (przesuwając ją od prawej do lewej) kilka razy równą potędze dziesięciu, pomnożąc licznik. Jest to prosty sposób uzyskiwania wyników zmiennoprzecinkowych przy użyciu tylko liczb całkowitych.


1
Nawet Busybox ma Awk. Być może powinna tu być bardziej widoczna odpowiedź Awk.
tripleee

4

Chociaż nie można używać podziału zmiennoprzecinkowego w Bash, można zastosować podział stały. Wszystko, co musisz zrobić, to pomnożyć liczby całkowite przez potęgę 10, a następnie oddzielić część całkowitą i użyć operacji modulo, aby uzyskać część ułamkową. Zaokrąglanie w razie potrzeby.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))

4

wiem, że jest stary, ale zbyt kuszący. więc odpowiedź brzmi: nie możesz ... ale możesz. Spróbujmy tego:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

w ten sposób dostajesz 2 cyfry po punkcie, obcięte (nazwij to zaokrąglaniem do niższej, haha) w czystym bashu (nie musisz uruchamiać innych procesów). oczywiście, jeśli potrzebna jest tylko jedna cyfra po punkcie, mnożymy przez 10 i robimy modulo 10.

co to robi:

  • pierwsze $ ((...)) dokonuje podziału na liczby całkowite;
  • second $ ((...)) dokonuje podziału liczb całkowitych na czymś 100 razy większym, zasadniczo przesuwając twoje 2 cyfry na lewo od punktu, a następnie (%) otrzymując tylko te 2 cyfry, wykonując modulo.

bonusowy utwór : wersja bc x 1000 zajęła 1,8 sekundy na moim laptopie, podczas gdy czysty bash zajął 0,016 sekundy.


3

Jak wykonać obliczenia zmiennoprzecinkowe w bash:

Zamiast używać „ tutaj ciągów ” ( <<<) z bcpoleceniem, tak jak robi to jeden z najbardziej popularnych przykładów , oto mój ulubiony bcprzykład zmiennoprzecinkowy, bezpośrednio z EXAMPLESsekcji bcstron podręcznika man (zobaczman bc podręcznika strony podręcznika).

Zanim zaczniemy, wiedzą, że równanie dla PI: pi = 4*atan(1). a()poniżej znajduje się bcfunkcja matematyczna dla atan().

  1. W ten sposób zapisujesz wynik obliczeń zmiennoprzecinkowych w zmiennej bash - w tym przypadku w zmiennej o nazwiepi . Zauważ, że scale=10w tym przypadku ustawia liczbę cyfr dziesiętnych dokładności na 10. Wszelkie cyfry dziesiętne po tym miejscu są obcinane .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Teraz, aby mieć jeden wiersz kodu, który również wypisuje wartość tej zmiennej, po prostu dodaj echopolecenie na końcu jako polecenie uzupełniające, w następujący sposób. Zwróć uwagę na obcięcie z 10 miejscami po przecinku, zgodnie z rozkazem:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Na koniec wrzućmy zaokrąglenie. Tutaj użyjemy printffunkcji do zaokrąglenia do 4 miejsc po przecinku. Zauważ, że 3.14159...rundy teraz do 3.1416. Ponieważ zaokrąglamy, nie musimy już używać scale=10przycinania do 10 miejsc po przecinku, więc po prostu usuniemy tę część. Oto końcowe rozwiązanie:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Oto kolejna naprawdę świetna aplikacja i prezentacja powyższych technik: pomiar i drukowanie czasu wykonywania.

Uwaga: dt_minzaokrągla się 0.01666666666...do 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Związane z:


1
solidna odpowiedź ze szczegółowymi przykładami i przypadkami użycia i okrągłe przy użyciu printf
downvoteit


2

Dla tych, którzy próbują obliczyć procenty z zaakceptowaną odpowiedzią, ale tracą precyzję:

Jeśli uruchomisz to:

echo "scale=2; (100/180) * 180" | bc

Dostajesz 99.00tylko to, co traci precyzję.

Jeśli uruchomisz to w ten sposób:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Teraz masz 99.99.

Ponieważ skalujesz tylko w momencie drukowania.

Zobacz tutaj


1

** Bezpieczna wtryskowa matematyka zmiennoprzecinkowa w bash / shell **

Uwaga: Celem tej odpowiedzi jest dostarczenie pomysłów na bezpieczne dla iniekcji rozwiązanie wykonywania matematyki w bash (lub innych powłokach). Oczywiście to samo można zastosować, z niewielkim dostosowaniem w celu przeprowadzenia zaawansowanego przetwarzania łańcucha itp.

Większość przedstawionych rozwiązań polega na tworzeniu małych skryptletów w locie przy użyciu danych zewnętrznych (zmiennych, plików, wiersza poleceń, zmiennych środowiskowych). Zewnętrzne dane wejściowe można wykorzystać do wstrzyknięcia złośliwego kodu do silnika, wielu z nich

Poniżej znajduje się porównanie używania różnych języków do wykonywania podstawowych obliczeń matematycznych, których wynikiem jest liczba zmiennoprzecinkowa. Oblicza A + B * 0,1 (jako zmiennoprzecinkowy).

Wszystkie próby rozwiązania unikają tworzenia skryptletów dynamicznych, które są niezwykle trudne w utrzymaniu, zamiast tego używają programu statycznego i przekazują parametry do wyznaczonej zmiennej. Będą bezpiecznie obsługiwać parametry ze znakami specjalnymi - zmniejszając możliwość wstrzyknięcia kodu. Wyjątkiem jest „BC”, który nie zapewnia funkcji wejścia / wyjścia

Wyjątkiem jest „bc”, który nie zapewnia żadnych danych wejściowych / wyjściowych, wszystkie dane są przesyłane przez programy w stdin, a wszystkie dane wyjściowe są przesyłane do stdout. Wszystkie obliczenia są wykonywane w piaskownicy, co nie pozwala na efekt uboczny (otwieranie plików itp.). Teoretycznie wtrysk bezpieczny z założenia!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"

dla python3 z dowolnymi argumentami: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B"„4200.0” ==> 4205.63
WiringHarness

0

oto polecenie awk: -F = separator pól == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
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.