Làm thế nào để làm tròn số thập phân bằng bc trong bash?


45

Một ví dụ nhanh về những gì tôi muốn sử dụng 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

Giả sử giá là: 48,86 Câu trả lời sẽ là: 41,406779661 (thực tế là 41,40 vì tôi đang sử dụng scale=2;)

Câu hỏi của tôi là: Làm thế nào tôi làm tròn số thập phân thứ hai để hiển thị câu trả lời theo cách này?: 41.41


Tôi thấy lạ vì "printf"% 0.2f \ n "41.445" hiện không hoạt động nhưng "printf"% 0.2f \ n "41.435 và printf"% 0.2f \ n "41.455". Ngay cả trường hợp của riêng bạn cũng hoạt động (Ngày 12.04) nhưng không phải với .445
Luis Alvarado

8
IMHO, không ai trả lời thỏa đáng câu hỏi này , có lẽ vì bckhông thể đạt được những gì đang được yêu cầu (hoặc ít nhất là câu hỏi tôi đã hỏi khi tìm thấy bài đăng này), đó là cách làm tròn số thập phân bằng cách sử dụngbc (điều đó xảy ra để được gọi bởi bash).
Adam Katz

Tôi biết câu hỏi này đã cũ, nhưng tôi không thể cưỡng lại một bình luận thúc đẩy việc thực hiện bc: github.com/gavinhoward/bc của riêng tôi . Cho rằng, nó nằm trong thư viện tích hợp với r()chức năng, vì vậy bạn có thể sử dụng bc -le "r($float/1.18, 2)".
Gavin Howard

Câu trả lời:


31

Hàm bash round:

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

Được sử dụng trong ví dụ mã của bạn:

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

Chúc may mắn: o)


1
btw, nếu số âm, chúng ta phải sử dụng-0.5
Sức mạnh Bảo Bình

Không thể lấy ví dụ để làm việc trên bảng điều khiển thử nghiệm! "Gỡ lỗi" một chút tiết lộ rằng ví dụ $2 = 3tôi phải sử dụng echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Tâm trí env trước printf! Điều này sẽ dạy bạn một lần nữa rằng điều quan trọng là phải hiểu những gì bạn đang sao chép từ nơi khác.
cú pháp

@Aquarius Sức mạnh cảm ơn cho cảm hứng! Bây giờ tôi đã chọn một phiên bản của tập lệnh trên sẽ hoạt động với cả số âm và số dương.
cú pháp

Điều này là phức tạp không cần thiết và làm tất cả các loại số học bổ sung để đi đến các câu trả lời đơn giản hơn được cung cấp bởi Migas và zuberuber.
Adam Katz

@AquariusPower +1 có, nó khác với các số âm. Để kiểm tra số âm (không nguyên!), Tôi đã thử một chút và giải quyết cho if [ tiếng vang "$ 1/1" | bc` -gt 0] `- có cách nào thanh lịch hơn để kiểm tra (ngoại trừ phân tích chuỗi cho" - ") không?
RobertG

30

Giải pháp đơn giản nhất:

printf %.2f $(echo "$float/1.18" | bc -l)

2
Như đã lưu ý trong một nhận xét ở trên ( Askubfox.com/a/179949/512213 ), điều này sẽ hành xử không chính xác cho các số âm, thật không may.
RobertG

2
@RobertG: Tại sao? Giải pháp này không sử dụng +0.5. Hãy thử printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"và bạn sẽ nhận được 41.41-41.41.
musiphil

1
Cũng có thể sử dụng một đường ống:echo "$float/1.18" | bc -l | xargs printf %.2f
tráng miệng

Giải pháp trong câu trả lời này cho tôi : bash: printf: 1.69491525423728813559: invalid number, tùy thuộc vào miền địa phương.
Torsten Bronger

19

Bash / awk làm tròn:

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

Nếu bạn có python, bạn có thể sử dụng một cái gì đó như thế này:

echo "4.678923" | python -c "print round(float(raw_input()))"

Cảm ơn vì tiền boa nhưng nó không giải quyết được những gì tôi cần. Tôi phải làm điều đó chỉ bằng cách sử dụng bash ...
blackedx

1
Lệnh python dễ đọc hơn và tốt cho các kịch bản nhanh. Cũng hỗ trợ làm tròn chữ số tùy ý bằng cách thêm ví dụ ", 3" vào hàm làm tròn. Cảm ơn
Elle

echo "$float" |awk '{printf "%.2f", $1/1.18}'sẽ thực hiện phép toán được yêu cầu của câu hỏi cho phần trăm được yêu cầu. Đó là "sử dụng bash" nhiều như bccuộc gọi trong câu hỏi.
Adam Katz

2
Đối với Python bạn chỉ có thể sử dụng một cái gì đó như python -c "print(round($num))"ở đâu num=4.678923. Không cần phải loay hoay với stdin. Bạn cũng có thể làm tròn đến n chữ số như vậy : python -c "print(round($num, $n))".
Sáu

Tôi đã xoay sở để làm một cái gì đó khá gọn gàng với giải pháp đó, vấn đề của tôi là 0,5, trong trường hợp này tôi không phải lúc nào cũng có được vòng mình cần nên tôi đã đưa ra những điều sau: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Khi + sẽ làm tròn và - làm tròn xuống).
Yaron

4

Đây là một giải pháp bc hoàn toàn. Quy tắc làm tròn: tại +/- 0,5, làm tròn từ số không.

Đặt tỷ lệ bạn đang tìm kiếm trong $ result_scale; toán học của bạn phải là nơi $ MATH nằm trong danh sách lệnh 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
rất thú vị! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 như được giải thích ở đây :)
Sức mạnh của Bảo Bình

Tôi đề cử điều này cho "câu trả lời tốt nhất".
Marc Tamsky

2

Tôi biết đó là một câu hỏi cũ, nhưng tôi có một giải pháp 'bc' thuần túy mà không có' nếu 'hoặc các nhánh:

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

Sử dụng nó như bcr '2/3' 5hoặc bcr '0.666666' 2-> (biểu thức theo tỷ lệ)

Điều đó là có thể bởi vì trong bc (như C / C ++), nó được phép trộn các biểu thức logic trong tính toán của bạn. Biểu thức ((t>0)-(t<0))/2)sẽ ước tính thành +/- 0,5 tùy thuộc vào dấu của 't' và do đó sử dụng giá trị đúng để làm tròn.


Điều này có vẻ gọn gàng, nhưng bcr "5 / 2" 0trả lại 2thay vì 3. Tui bỏ lỡ điều gì vậy?
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..."

Nó thực sự đơn giản: không cần phải thêm một biến thể "-0,5" được mã hóa cứng cho các số âm. Nói một cách toán học, chúng ta sẽ chỉ tính giá trị tuyệt đối của đối số và vẫn cộng 0,5 như bình thường. Nhưng vì chúng tôi (không may) không có abs()chức năng tích hợp sẵn theo ý của chúng tôi (trừ khi chúng tôi mã hóa một), chúng tôi sẽ đơn giản phủ nhận đối số nếu nó phủ định.

Bên cạnh đó, nó tỏ ra rất cồng kềnh khi làm việc với thương số như một tham số (vì đối với giải pháp của tôi, tôi phải có thể truy cập vào cổ tức và ước số riêng). Đây là lý do tại sao tập lệnh của tôi có thêm một tham số thứ ba.


@muru về các chỉnh sửa gần đây của bạn: herestrings thay vì echo .. |, shell số học, ý nghĩa của nó là echo $()gì? Điều này rất dễ để giải thích: chuỗi ở đây của bạn sẽ luôn yêu cầu một thư mục có thể ghi/tmp ! Và giải pháp của tôi cũng sẽ làm việc trên một môi trường chỉ đọc, ví dụ như một vỏ rễ khẩn cấp nơi /không luôn luôn có thể ghi theo mặc định. Vì vậy, có một lý do tốt tại sao tôi mã hóa nó theo cách đó.
cú pháp

Herestrings, tôi đồng ý. Nhưng echo $()bao giờ thì cần? Điều đó và sự thụt vào đã thúc đẩy các chỉnh sửa của tôi, các herestrings vừa xảy ra.
muru

@muru Vâng, trường hợp vô nghĩa nhất echo $()đã quá hạn để được sửa chữa trên thực tế là dòng áp chót, lol. Cảm ơn cho những người đứng đầu lên. Ít nhất thì cái này bây giờ trông đẹp hơn nhiều, tôi không thể phủ nhận. Dù sao, tôi yêu logic trong này. Rất nhiều công cụ boolean, "nếu" không cần thiết (điều này chắc chắn sẽ làm nổ mã 50%).
cú pháp

1

Tôi vẫn đang tìm kiếm một bccâu trả lời thuần túy về cách làm tròn chỉ một giá trị trong một hàm, nhưng đây là một bashcâu trả lời thuần túy :

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

Về cơ bản, điều này trích xuất cẩn thận các số thập phân, nhân mọi thứ với 100 tỷ (10¹⁰, 10**10in bash), điều chỉnh độ chính xác và làm tròn, thực hiện phép chia thực tế, chia lại cho độ lớn phù hợp và sau đó lắp lại số thập phân.

Từng bước một:

Các embiggen()chuyển nhượng chức năng cắt ngắn số nguyên dưới dạng đối số của nó để $intvà tiết kiệm các con số sau dấu chấm trong $fraction. Số chữ số phân số được ghi chú trong $precision. Multiplies toán 10¹⁰ bởi nối của $int$fractionvà sau đó điều chỉnh đó để phù hợp với độ chính xác (ví dụ như embiggen 48.86trở thành 10¹⁰ × 4886/100 và lợi nhuận 488600000000mà là 488.600.000.000).

Chúng tôi muốn độ chính xác cuối cùng là một phần trăm, vì vậy chúng tôi nhân số thứ nhất với 100, thêm 5 cho mục đích làm tròn, sau đó chia số thứ hai. Nhiệm vụ này $answerđể lại cho chúng tôi một trăm lần câu trả lời cuối cùng.

Bây giờ chúng ta cần thêm dấu thập phân. Chúng tôi gán một $intgiá trị mới để $answerloại trừ hai chữ số cuối cùng của nó, sau đó echolà một dấu chấm và $answerloại trừ $intgiá trị đã được xử lý. (Đừng bận tâm đến lỗi tô sáng cú pháp khiến lỗi này xuất hiện như một bình luận)

(Bashism: lũy thừa không phải là POSIX, vì vậy đây là một phép bashism. Một giải pháp POSIX thuần túy sẽ yêu cầu các vòng lặp để thêm số 0 thay vì sử dụng lũy ​​thừa mười. Ngoài ra, "embiggen" là một từ hoàn hảo.


Một trong những lý do chính mà tôi sử dụng zshlàm vỏ của mình là nó hỗ trợ toán học dấu phẩy động. Giải pháp cho câu hỏi này khá đơn giản trong zsh:

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

(Tôi rất muốn thấy ai đó thêm nhận xét cho câu trả lời này bằng mẹo để bật số học dấu phẩy động bash, nhưng tôi khá chắc chắn rằng một tính năng như vậy chưa tồn tại.)


1

nếu bạn có kết quả, ví dụ, hãy xem xét 2.3747888

tât cả nhưng điêu bạn phải lam la:

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

ví dụ này làm tròn số chính xác:

(2.49999 + 0.5)/1 = 2.99999 

các số thập phân được loại bỏ bcvà do đó nó làm tròn xuống 2 như nó cần phải có


1

Tôi đã phải tính tổng thời lượng của một tập hợp các tệp âm thanh.

Vì vậy, tôi đã phải:

A. có được thời lượng cho mỗi tệp ( không hiển thị )

B. cộng tất cả thời lượng (mỗi lần trong NNN.NNNNNN(fp) giây)

C. riêng giờ, phút, giây, giây.

D. xuất ra một chuỗi HR: MIN: SEC: FRAMES, trong đó frame = 1/75 giây.

(Khung đến từ mã SMPTE được sử dụng trong các studio.)


A: ffprobedòng thời gian sử dụng và phân tích thành một số fp (không hiển thị)

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 #

CƯỜI MỞ MIỆNG:

#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

Đây là phiên bản rút gọn của tập lệnh của bạn, được sửa để cung cấp đầu ra bạn muốn:

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

Lưu ý rằng làm tròn đến số nguyên gần nhất tương đương với việc thêm 0,5 và lấy số sàn hoặc làm tròn xuống (đối với số dương).

Ngoài ra, hệ số tỷ lệ được áp dụng tại thời điểm hoạt động; vì vậy (đây là bccác lệnh, bạn có thể dán chúng vào thiết bị đầu cuối của mình):

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

Thực hiện bc thuần túy theo yêu cầu

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

nếu bạn đặt nó trong một tệp có tên là tests.bc thì bạn có thể làm tròn với

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

Mã cho việc triển khai bc được tìm thấy trên 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

Bạn có thể sử dụng awk, không cần ống:

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

Đặt giá trước với biến môi trường PRICE=<val>.

  • Sau đó gọi awk - $PRICEsẽ đi vào awk như một biến awk price.

  • Sau đó, nó được làm tròn đến 100 gần nhất với +.005.

  • Tùy chọn định dạng printf %.2fgiới hạn tỷ lệ ở hai vị trí thập phân.

Nếu bạn phải làm điều này rất nhiều thì cách này hiệu quả hơn rất nhiều so với câu trả lời được chọn:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.