Bagaimana cara membulatkan desimal menggunakan bc di bash?

45

Contoh singkat tentang apa yang saya inginkan menggunakan bash scripting:

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

Dengan asumsi bahwa harganya adalah: 48,86 Jawabannya adalah: 41,406779661 (41,40 sebenarnya karena saya menggunakan scale=2;)

Pertanyaan saya adalah: Bagaimana saya membulatkan desimal kedua untuk menunjukkan jawaban dengan cara ini ?: 41.41

blackedx
sumber
Saya merasa aneh karena "printf"% 0.2f \ n "41.445" sekarang berfungsi tetapi "printf"% 0.2f \ n "41.435 dan printf"% 0.2f \ n "41.455" dapat melakukannya. Bahkan kasing Anda sendiri berfungsi (pada 12.04) tetapi tidak dengan .445
Luis Alvarado
8
IMHO, tidak ada yang menjawab pertanyaan ini dengan memuaskan , mungkin karena bctidak dapat mencapai apa yang diminta (atau setidaknya pertanyaan yang saya tanyakan ketika saya menemukan posting ini), yaitu bagaimana cara membulatkan desimal menggunakanbc (yang kebetulan dipanggil oleh bash).
Adam Katz
Saya tahu pertanyaan ini sudah lama, tetapi saya tidak bisa menolak komentar yang mendorong implementasi bc saya sendiri: github.com/gavinhoward/bc . Untuk itu, ada di perpustakaan built-in dengan r()fungsi, sehingga Anda dapat menggunakannya bc -le "r($float/1.18, 2)".
Gavin Howard

Jawaban:

31

Fungsi bash round:

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

Digunakan dalam contoh kode Anda:

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

Semoga beruntung: o)

pengguna85321
sumber
1
btw, jika angkanya negatif, kita harus menggunakan-0.5
Aquarius Power
Tidak dapat membuat contoh berfungsi pada konsol pengujian! "Debugging" sedikit mengungkapkan bahwa untuk misalnya $2 = 3saya harus menggunakan echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Pikirkan env sebelum printf! Ini akan mengajari Anda lagi bahwa selalu penting untuk memahami apa yang Anda salin dari tempat lain.
syntaxerror
@Aquarius Power terima kasih atas ilhamnya! Saya sekarang telah bercabang versi naskah di atas yang akan bekerja dengan angka negatif dan positif.
syntaxerror
Ini tidak perlu rumit dan melakukan semua jenis aritmatika tambahan untuk sampai pada jawaban sederhana yang disediakan oleh migas dan zuberuber.
Adam Katz
@AquariusPower +1 ya, ini berbeda untuk angka negatif. Untuk menguji angka negatif (bukan bilangan bulat!), Saya mencoba sedikit dan memilih if [ echo "$ 1/1" | bc` -gt 0] `- apakah ada cara yang lebih elegan untuk memeriksa (kecuali menguraikan string untuk" - ")?
RobertG
30

Solusi paling sederhana:

printf %.2f $(echo "$float/1.18" | bc -l)
migas
sumber
2
Seperti disebutkan juga dalam komentar di atas ( askubuntu.com/a/179949/512213 ), sayangnya ini akan berperilaku salah untuk angka negatif.
RobertG
2
@ RobertTG: Mengapa? Solusi ini tidak digunakan +0.5. Cobalah printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"dan Anda akan mendapatkan 41.41dan -41.41.
musiphil
1
Juga dimungkinkan menggunakan pipa:echo "$float/1.18" | bc -l | xargs printf %.2f
hidangan penutup
Solusi dalam jawaban ini memberi saya bash: printf: 1.69491525423728813559: invalid number:, tergantung pada lokal.
Torsten Bronger
19

Bash / awk rounding:

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

Jika Anda memiliki python, Anda dapat menggunakan sesuatu seperti ini:

echo "4.678923" | python -c "print round(float(raw_input()))"
zuberuber
sumber
Terima kasih atas tipnya tetapi itu tidak menyelesaikan apa yang saya butuhkan. Saya harus melakukannya hanya dengan menggunakan bash ...
blackedx
1
Perintah python lebih mudah dibaca dan bagus untuk skrip cepat. Juga mendukung pembulatan digit sembarang dengan menambahkan misalnya ", 3" ke fungsi putaran. Terima kasih
Elle
echo "$float" |awk '{printf "%.2f", $1/1.18}'akan melakukan matematika yang diminta pertanyaan untuk percision yang diminta dari seratus. Itu sebanyak "menggunakan bash" sebagai bcpanggilan dalam pertanyaan.
Adam Katz
2
Untuk Python Anda bisa menggunakan sesuatu seperti di python -c "print(round($num))"mana num=4.678923. Tidak perlu bercanda dengan stdin. Anda juga dapat bulat untuk n digit suka begitu: python -c "print(round($num, $n))".
Enam
Saya berhasil melakukan sesuatu yang cukup rapi dengan solusi itu, masalah saya adalah 0,5, dalam hal ini saya tidak selalu mendapatkan putaran yang saya butuhkan jadi saya datang dengan yang berikut: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Ketika + akan mengumpulkan dan - bulat ke bawah).
Yaron
4

Ini solusi murni bc. Aturan pembulatan: pada +/- 0,5, bulatkan dari nol.

Masukkan skala yang Anda cari dalam $ result_scale; matematika Anda seharusnya berada di tempat $ MATH berada di daftar perintah 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
sumber
1
sangat menarik! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 seperti yang dijelaskan di sini :)
Aquarius Power
Saya mencalonkan ini untuk "jawaban terbaik".
Marc Tamsky
2

Saya tahu ini pertanyaan lama, tapi saya punya solusi murni 'bc' tanpa 'jika' atau cabang:

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

Gunakan suka bcr '2/3' 5atau bcr '0.666666' 2-> (ekspresi diikuti oleh skala)

Itu mungkin karena dalam bc (seperti C / C ++) itu diperbolehkan untuk mencampur ekspresi logis dalam perhitungan Anda. Ekspresi ((t>0)-(t<0))/2)akan mengevaluasi ke +/- 0,5 tergantung pada tanda 't' dan karenanya menggunakan nilai yang tepat untuk pembulatan.

Marcus Kolenda
sumber
Ini terlihat rapi, tetapi bcr "5 / 2" 0kembali 2bukan 3. Apakah saya melewatkan sesuatu?
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..."

Ini sebenarnya sederhana: tidak perlu menambahkan secara eksplisit varian "-0,5" yang telah di-hardcod untuk angka negatif. Secara matematis, kita hanya akan menghitung nilai absolut dari argumen dan tetap menambahkan 0,5 seperti biasa. Tetapi karena kita (sayangnya) tidak memiliki abs()fungsi bawaan yang kita miliki (kecuali kita kode satu), kita hanya akan meniadakan argumen jika negatif.

Selain itu, terbukti sangat rumit untuk bekerja dengan hasil bagi sebagai parameter (karena untuk solusi saya, saya harus dapat mengakses dividen dan pembagi secara terpisah). Inilah sebabnya mengapa skrip saya memiliki parameter ketiga tambahan.

kesalahan sintaks
sumber
@muru tentang suntingan terakhir Anda: herestrings alih-alih echo .. |, shell aritmatika, apa gunanya echo $()? Ini mudah dijelaskan: string Anda di sini akan selalu membutuhkan direktori yang dapat ditulis/tmp ! Dan solusi saya juga akan bekerja pada lingkungan read-only, misalnya sebuah shell root darurat di mana /adalah tidak selalu ditulis secara default. Jadi ada alasan bagus mengapa saya membuat kode seperti itu.
syntaxerror
Herestrings, saya setuju. Tetapi kapan echo $()dibutuhkan? Itu dan lekukan mendorong suntingan saya, herestrings baru saja terjadi.
muru
@uru Nah kasus yang paling tidak berguna dari echo $()yang sudah terlambat untuk diperbaiki sebenarnya adalah garis kedua dari belakang, lol. Terimakasih atas peringatannya. Setidaknya yang satu ini memang terlihat jauh lebih baik sekarang, saya tidak dapat menyangkal. Bagaimanapun, saya menyukai logika dalam hal ini. Banyak hal boolean, "not" berlebihan (yang pasti akan meledakkan kode sebesar 50 persen).
syntaxerror
1

Saya masih mencari bcjawaban murni untuk cara membulatkan hanya satu nilai dalam suatu fungsi, tapi inilah bashjawaban murni :

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

Pada dasarnya, ini dengan hati-hati mengekstraksi desimal, mengalikan semuanya dengan 100 miliar (10¹⁰, 10**10dalam bash), menyesuaikan presisi dan pembulatan, melakukan pembagian aktual, membagi kembali ke besaran yang sesuai, dan kemudian memasukkan kembali desimal.

Selangkah demi selangkah:

The embiggen()ditunjuk fungsi dipotong bulat bentuk argumen untuk $intdan menyimpan nomor setelah titik di $fraction. Jumlah digit fraksional dicatat dalam $precision. Matematika dikalikan 10¹⁰ dengan gabungan dari $intdan $fractionkemudian menyesuaikannya agar sesuai dengan presisi (misalnya embiggen 48.86menjadi 10¹⁰ × 4886/100 dan menghasilkan 488600000000488.600.000.000).

Kami menginginkan ketepatan akhir dari seratus, jadi kami gandakan angka pertama dengan 100, tambahkan 5 untuk tujuan pembulatan, dan kemudian bagi angka kedua. Tugas ini $answermembuat kita seratus kali jawaban terakhir.

Sekarang kita perlu menambahkan titik desimal. Kami menetapkan $intnilai baru untuk $answermengecualikan dua digit terakhirnya, lalu echodengan titik dan $answertidak termasuk $intnilai yang sudah diurus. (Jangankan bug yang menyoroti sintaksis yang membuat ini tampak seperti komentar)

(Bashism: exponentiation bukan POSIX, jadi ini adalah bashism. Solusi POSIX murni akan membutuhkan loop untuk menambahkan nol daripada menggunakan kekuatan sepuluh. Juga, "embiggen" adalah kata yang sangat cromulant.)


Salah satu alasan utama yang saya gunakan zshsebagai shell saya adalah bahwa ia mendukung matematika floating point. Solusi untuk pertanyaan ini cukup mudah di zsh:

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

(Saya ingin melihat seseorang menambahkan komentar pada jawaban ini dengan trik untuk mengaktifkan aritmatika floating point bash, tetapi saya cukup yakin bahwa fitur seperti itu belum ada.)

Adam Katz
sumber
1

jika Anda memiliki hasilnya, misalnya pertimbangkan 2.3747888

Yang harus Anda lakukan adalah:

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

ini membulatkan angka dengan benar contoh:

(2.49999 + 0.5)/1 = 2.99999 

desimal dihilangkan oleh bcdan jadi dibulatkan menjadi 2 seperti seharusnya

pengguna525061
sumber
1

Saya harus menghitung total durasi koleksi file audio.

Jadi saya harus:

A. dapatkan durasi untuk setiap file ( tidak ditampilkan )

B. menjumlahkan semua durasi (masing-masing dalam NNN.NNNNNN(fp) detik)

C. jam terpisah, menit, detik, subseconds.

D. output string HR: MIN: SEC: FRAMES, di mana frame = 1/75 detik.

(Frame berasal dari kode SMPTE yang digunakan di studio.)


A: gunakan ffprobedan parsing garis durasi ke nomor fp (tidak ditampilkan)

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"
Chris Reid
sumber
1

Ini versi singkat dari skrip Anda, diperbaiki untuk memberikan hasil yang Anda inginkan:

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

Perhatikan bahwa pembulatan ke bilangan bulat terdekat sama dengan menambahkan 0,5 dan mengambil lantai, atau pembulatan ke bawah (untuk angka positif).

Juga, faktor skala diterapkan pada saat operasi; jadi (ini adalah bcperintah, Anda dapat menempelkannya ke terminal Anda):

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
sumber
1

Implementasi BC murni seperti yang diminta

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

jika Anda memasukkannya ke dalam file bernama functions.bc maka Anda dapat menambahkannya

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

Kode untuk implementasi bc ditemukan di http://phodd.net/gnu-bc/code/funcs.bc

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

Anda dapat menggunakan awk, tidak perlu pipa:

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

Tetapkan harga sebelumnya dengan variabel lingkungan PRICE=<val>.

  • Kemudian panggil awk - $PRICEakan masuk ke awk sebagai variabel awk price.

  • Ini kemudian dibulatkan ke 100 terdekat dengan +,005.

  • Opsi format printf %.2fmembatasi skala hingga dua tempat desimal.

Jika Anda harus sering melakukan ini, cara ini jauh lebih efisien daripada jawaban yang dipilih:

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
sumber