Bagaimana cara menggunakan divisi floating-point di bash?

242

Saya mencoba untuk membagi dua lebar gambar dalam skrip Bash, tetapi bash memberi saya 0 hasilnya :

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

Saya mempelajari panduan Bash dan saya tahu saya harus menggunakan bc, dalam semua contoh di internet yang mereka gunakan bc. Diecho Aku mencoba untuk menempatkan hal yang sama di saya SCALE, tapi tidak berhasil.

Berikut adalah contoh yang saya temukan di tutorial:

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

Bagaimana saya bisa membuat Bash memberi saya pelampung 0.5?

Medya Gh
sumber
9
Sebuah komentar untuk semua orang yang mencoba melakukan aritmatika floating point dalam skrip Anda, tanyakan pada diri sendiri: apakah saya benar-benar membutuhkan aritmatika floating point? terkadang Anda benar-benar dapat bergaul tanpa. Lihat, misalnya bagian terakhir dari BashFAQ / 022 .
gniourf_gniourf

Jawaban:

242

Kamu tidak bisa bash hanya melakukan integer; Anda harus mendelegasikan ke alat seperti bc.

Ignacio Vazquez-Abrams
sumber
8
bagaimana saya bisa mendelegasikan alat seperti bc untuk memasukkan jawaban dalam variabel HASIL?
Medya Gh
2
jadi maksudmu seperti VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh
54
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)atau VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")tanpa $ (()) (tanda kurung ganda); yang diperluas oleh bash sebelum menjalankan perintah
Nahuel Fouilleul
4
Benar, tetapi awk umumnya lebih mungkin sudah diinstal dalam sistem.
CMCDragonkai
9
@NahuelFouilleul Anda punya jawaban terbaik di sini. Seharusnya benar-benar jawabannya sendiri, dan diterima sebagai jawabannya. Baris khusus ini sangat berguna: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David
197

kamu bisa melakukan ini:

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

PEMBARUAN 20130926 : Anda dapat menggunakan:

bc -l <<< '100/3' # saves a few hits
aayoubi
sumber
8
... dan menambahkan banyak digit. Setidaknya pada mesin saya ini benang 33.33333333333333333333 sedangkan yang pertama memberi 33.33.
Andreas Spindler
12
@AndreasSpindler Jenis posting lama, tetapi jika ada orang yang ingin tahu, ini dapat diubah dengan menerapkan perintah skala mis. bc -l <<< 'scale=2; 100/3'
Martie
Berhati-hatilah jika Anda berharap mendapatkan bilangan bulat untuk digunakan nanti di bash dengan menggunakan skala = 0. Pada v1.06.95, bc, karena alasan tertentu, mengabaikan variabel skala ketika angka input memiliki bagian desimal. Mungkin ini ada di dokumen, tetapi saya tidak dapat menemukannya. Coba: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell
1
@GregBell Halaman manual mengatakan Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.Dan ada catatan tambahan untuk /operator:The scale of the result is the value of the variable scale.
psmith
2
Terima kasih @smith. Menariknya, untuk / dikatakan "skala hasil adalah nilai skala variabel" tetapi tidak demikian untuk perkalian. Contoh saya yang lebih baik: bc <<< 'scale=1; 1*3.00001' skala benar-benar 5 untuk beberapa alasan, bc <<< 'scale=1; 1/3.000001' skala adalah 1. Menariknya, membaginya dengan 1 set langsung: bc <<< 'scale=1; 1*3.00001/1'skala adalah 1
Greg Bell
136

pesta

Seperti dicatat oleh orang lain, bashtidak mendukung aritmatika titik mengambang, meskipun Anda bisa memalsukannya dengan beberapa tipuan desimal tetap, misalnya dengan dua desimal:

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

Keluaran:

.33

Lihat jawaban Nilfred untuk pendekatan yang serupa tetapi lebih ringkas.

Alternatif

Selain yang disebutkan bcdan awkalternatif ada juga yang berikut:

clisp

clisp -x '(/ 1.0 3)'

dengan output dibersihkan:

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

atau melalui stdin:

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

dc

echo 2k 1 3 /p | dc

kalkulator genius 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

Atau:

jq -n 1/3

ksh

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

lua

lua -e 'print(1/3)'

atau melalui stdin:

echo 'print(1/3)' | lua

maksimal

echo '1/3,numer;' | maxima

dengan output dibersihkan:

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

simpul

echo 1/3 | node -p

oktaf

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

dengan output dibersihkan:

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

rubi

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Dengan output yang dibersihkan:

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

zsh

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

unit

units 1/3

Dengan output yang kompak:

units --co 1/3

Sumber lainnya

Stéphane Chazelas menjawab pertanyaan serupa di Unix.SX.

Thor
sumber
9
Jawaban yang bagus Saya tahu itu diposting beberapa tahun setelah pertanyaan tetapi lebih layak menjadi jawaban yang diterima.
Brian Cline
2
Jika Anda memiliki zsh yang tersedia, maka ada baiknya mempertimbangkan untuk menulis skrip Anda di zsh bukannya Bash
Andrea Corbellini
Jempol untuk gnuplot :)
andywiecko
Daftar yang bagus. Terutama echo 1/3 | node -ppendek.
Johnny Wong
39

Meningkatkan sedikit jawaban dari marvin:

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

bc tidak datang selalu sebagai paket yang diinstal.

adrianlzt
sumber
4
Script awk perlu exitmencegah agar tidak membaca dari aliran inputnya. Saya juga menyarankan menggunakan -vbendera awk untuk mencegah sindrom tusuk gigi. Jadi:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley
2
Cara yang lebih awkish untuk melakukan hal ini akan membaca argumen dari input stream: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
Jmster
1
bcadalah bagian dari POSIX, biasanya sudah diinstal sebelumnya.
fuz
ini bekerja untuk saya menggunakan bash git di windows 7 ... terima kasih :)
The Beast
31

Anda dapat menggunakan bc dengan -lopsi (huruf L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
pengguna1314742
sumber
4
Jika saya tidak memasukkan -lpada sistem saya, bc tidak melakukan matematika floating point.
starbeamrainbowlabs
28

Sebagai alternatif untuk bc, Anda dapat menggunakan awk dalam skrip Anda.

Sebagai contoh:

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

Dalam contoh di atas, "% .2f" memberi tahu fungsi printf untuk mengembalikan angka floating point dengan dua digit setelah tempat desimal. Saya menggunakan gema untuk pipa dalam variabel sebagai bidang karena awk beroperasi dengan benar pada mereka. "$ 1" dan "$ 2" merujuk ke input bidang pertama dan kedua ke awk.

Dan Anda dapat menyimpan hasilnya karena beberapa variabel lain menggunakan:

RESULT = `echo ...`
marvin
sumber
Luar biasa! Terima kasih. Ini bermanfaat untuk lingkungan tertanam di mana bc tidak ada. Anda menghemat waktu kompilasi lintas.
Antusiasme
19

Ini adalah waktu yang tepat untuk mencoba zsh, superset (hampir) bash, dengan banyak fitur bagus tambahan termasuk matematika floating point. Berikut adalah contoh Anda di zsh:

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

Posting ini dapat membantu Anda: bash - Layak beralih ke zsh untuk penggunaan biasa?

Penghe Geng
sumber
3
Saya penggemar berat zsh dan telah menggunakannya selama 4 tahun terakhir, tetapi penggunaan interaktif adalah penekanan yang baik di sini. Sebuah skrip yang membutuhkan zsh biasanya tidak akan sangat portabel di berbagai mesin karena biasanya tidak standar, sayangnya (untuk bersikap adil, mungkin tidak apa-apa; OP tidak mengatakan secara pasti bagaimana itu akan digunakan).
Brian Cline
17

Nah, sebelum float adalah waktu di mana logika desimal tetap digunakan:

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

Baris terakhir adalah bashim, jika tidak menggunakan bash, coba kode ini sebagai gantinya:

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

Alasan di balik kode adalah: kalikan dengan 100 sebelum bagi untuk mendapatkan 2 desimal.

Nilfred
sumber
5

Jika Anda menemukan varian preferensi Anda, Anda juga dapat membungkusnya menjadi suatu fungsi.

Di sini saya membungkus beberapa bashism ke dalam fungsi div:

Satu liner:

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;}

Atau multi baris:

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
}

Sekarang Anda memiliki fungsi

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

dan menggunakannya seperti

> div 1 2
.50

> div 273 123 5
2.21951

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

MEMPERBARUI Saya menambahkan skrip kecil yang memberi Anda operasi dasar dengan angka floating point untuk bash:

Pemakaian:

> 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

Dan di sini skripnya:

#!/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 / ;}
bebbo
sumber
1
local _d=${3:-2}lebih sederhana
KamilCuk
4

Ini bukan benar-benar titik mengambang, tetapi jika Anda menginginkan sesuatu yang menetapkan lebih dari satu hasil dalam satu doa ...

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

menghitung luas dan diameter lingkaran yang radiusnya diberikan dalam $ 1

pengguna3210339
sumber
4

Ada beberapa skenario di mana Anda tidak dapat menggunakan bc karena itu mungkin tidak ada, seperti dalam beberapa versi pengurangan busybox atau sistem embedded. Dalam kasus apa pun, membatasi ketergantungan luar selalu merupakan hal yang baik untuk dilakukan sehingga Anda selalu dapat menambahkan nol ke angka yang dibagi dengan (pembilang), yang sama dengan mengalikan dengan kekuatan 10 (Anda harus memilih kekuatan 10 sesuai dengan presisi yang Anda butuhkan), yang akan membuat output divisi bilangan bulat. Setelah bilangan bulat itu memperlakukannya sebagai string dan posisikan titik desimal (memindahkannya dari kanan ke kiri) beberapa kali sama dengan kekuatan sepuluh Anda mengalikan pembilang dengan. Ini adalah cara sederhana untuk mendapatkan hasil float dengan hanya menggunakan angka integer.

Daniel J.
sumber
1
Bahkan Busybox memiliki Awk. Mungkin harus ada jawaban Awk yang lebih menonjol di sini.
tripleee
4

Meskipun Anda tidak dapat menggunakan pembagian titik mengambang di Bash, Anda dapat menggunakan pembagian titik tetap. Yang perlu Anda lakukan adalah mengalikan bilangan bulat Anda dengan kekuatan 10 dan kemudian membagi bagian bilangan bulat dan menggunakan operasi modulo untuk mendapatkan bagian fraksional. Pembulatan sesuai kebutuhan.

#!/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))
G. Allen Morris III
sumber
4

Aku tahu ini sudah tua, tapi terlalu menggoda. jadi, jawabannya adalah: Anda tidak bisa ... tetapi Anda bisa. mari kita coba ini:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

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

seperti itu Anda mendapatkan 2 digit setelah titik, terpotong (sebut saja pembulatan ke yang lebih rendah, haha) dalam bash murni (tidak perlu meluncurkan proses lain). tentu saja, jika Anda hanya perlu satu digit setelah poin Anda kalikan dengan 10 dan lakukan modulo 10.

apa yang dilakukan:

  • $ pertama ((...)) melakukan pembagian integer;
  • $ kedua ((...)) melakukan pembagian integer pada sesuatu yang 100 kali lebih besar, pada dasarnya memindahkan 2 digit Anda ke kiri titik, lalu (%) membuat Anda hanya mendapatkan 2 digit tersebut dengan melakukan modulo.

bonus track : bc versi x 1000 membutuhkan 1,8 detik di laptop saya, sedangkan bash murni butuh 0,016 detik.

di bawah ini
sumber
3

Cara melakukan perhitungan floating point di bash:

Alih-alih menggunakan "di sini string " ( <<<) dengan bcperintah, seperti salah satu contoh yang paling banyak dipilih , inilah bccontoh floating point favorit saya , langsung dari EXAMPLESbagian bchalaman manual (lihatman bc untuk halaman manual).

Sebelum kita mulai, tahu bahwa persamaan untuk pi adalah: pi = 4*atan(1). a()di bawah ini adalah bcfungsi matematika untuk atan().

  1. Ini adalah bagaimana menyimpan hasil perhitungan floating point ke dalam variabel bash - dalam hal ini menjadi variabel yang disebutpi . Perhatikan bahwa scale=10set jumlah angka desimal presisi menjadi 10 dalam hal ini. Setiap angka desimal setelah tempat ini terpotong .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Sekarang, untuk memiliki satu baris kode yang juga mencetak nilai variabel ini, cukup tambahkan echoperintah sampai akhir sebagai perintah tindak lanjut, sebagai berikut. Perhatikan pemotongan pada 10 tempat desimal, seperti yang diperintahkan:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Akhirnya, mari kita lakukan pembulatan. Di sini kita akan menggunakan printffungsi untuk membulatkan ke 4 tempat desimal. Perhatikan bahwa 3.14159...putaran sekarang ke 3.1416. Karena kita membulatkan, kita tidak perlu lagi menggunakan scale=10untuk memotong ke 10 tempat desimal, jadi kita hanya akan menghapus bagian itu. Inilah solusi akhirnya:

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

Berikut ini adalah aplikasi hebat lainnya dan demo dari teknik-teknik di atas: mengukur dan mencetak run-time.

Perhatikan bahwa dt_mindibulatkan dari 0.01666666666...ke 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

Terkait:

Gabriel Staples
sumber
1
jawaban solid dengan contoh terperinci dan menggunakan case & round menggunakan printf
downvoteit
2

Gunakan kalk. Ini contoh termudah yang saya temukan:

calc 1 + 1

 2

calc 1/10

 0.1
Camila Pereira
sumber
2

Bagi mereka yang mencoba menghitung persentase dengan jawaban yang diterima, tetapi kehilangan presisi:

Jika Anda menjalankan ini:

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

Anda 99.00hanya mendapatkan , yang kehilangan presisi.

Jika Anda menjalankannya seperti ini:

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

Sekarang kamu mengerti 99.99.

Karena Anda scaling hanya pada saat mencetak.

Lihat di sini

Wadih M.
sumber
1

** Matematika floating point aman injeksi di bash / shell **

Catatan: Fokus dari jawaban ini adalah memberikan ide untuk solusi aman injeksi untuk melakukan matematika dalam bash (atau cangkang lainnya). Tentu saja, hal yang sama dapat digunakan, dengan sedikit penyesuaian untuk melakukan pemrosesan string lanjutan, dll.

Sebagian besar solusi yang disajikan, membangun scriptlet kecil dengan cepat, menggunakan data eksternal (variabel, file, baris perintah, variabel lingkungan). Input eksternal dapat digunakan untuk menyuntikkan kode berbahaya ke mesin, banyak di antaranya

Di bawah ini adalah perbandingan penggunaan berbagai bahasa untuk melakukan perhitungan matematika dasar, di mana hasilnya adalah floating point. Ini menghitung A + B * 0,1 (sebagai titik mengambang).

Semua solusi mencoba menghindari membuat skrip dinamis, yang sangat sulit untuk dipelihara, Alih-alih mereka menggunakan program statis, dan meneruskan parameter ke variabel yang ditunjuk. Mereka akan dengan aman menangani parameter dengan karakter khusus - mengurangi kemungkinan injeksi kode. Pengecualian adalah 'BC' yang tidak menyediakan fasilitas input / output

Pengecualian adalah 'bc', yang tidak menyediakan input / output, semua data datang melalui program di stdin, dan semua output masuk ke stdout. Semua perhitungan dijalankan dalam kotak pasir, yang tidak memungkinkan efek samping (membuka file, dll.). Secara teori, injeksi aman menurut desain!

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"
dash-o
sumber
untuk python3 dengan argumen arbitrer: 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

di sini adalah perintah awk: -F = pemisah bidang == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
koolwithk
sumber