วิธีปัดเศษทศนิยมโดยใช้ bc ใน bash?


45

ตัวอย่างสั้น ๆ ของสิ่งที่ฉันต้องการใช้สคริปต์ทุบตี:

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

สมมติว่าราคาคือ: 48.86 คำตอบจะเป็น: 41.406779661 (41.40 จริง ๆ เพราะฉันใช้scale=2;)

คำถามของฉันคือ: ฉันจะปัดทศนิยมที่สองเพื่อแสดงคำตอบด้วยวิธีนี้ได้อย่างไร: 41.41


ฉันพบว่ามันแปลกเพราะ "printf"% 0.2f \ n "41.445" ตอนนี้ทำงานได้ แต่ "printf"% 0.2f \ n "41.435 และ printf"% 0.2f \ n "41.455" ทำได้ แม้แต่กรณีของคุณเองก็ใช้งานได้ (ใน 12.04) แต่ไม่ใช่กับ. 445
Luis Alvarado

8
IMHO ไม่มีใครตอบคำถามนี้ได้อย่างน่าพอใจอาจเป็นเพราะbcไม่สามารถบรรลุสิ่งที่ขอ (หรืออย่างน้อยคำถามที่ฉันถามเมื่อฉันพบโพสต์นี้) ซึ่งเป็นวิธีปัดเศษทศนิยมโดยใช้bc (ที่เกิดขึ้นจะถูกเรียกโดยbash)
Adam Katz

ฉันรู้ว่าคำถามนี้เป็นรุ่นเก่า แต่ฉันไม่สามารถต้านทานความคิดเห็นผลักดันการดำเนินงานของตัวเอง BC: github.com/gavinhoward/bc เพื่อที่มันมีอยู่ในห้องสมุดในตัวที่มีฟังก์ชั่นเพื่อให้คุณสามารถใช้r() bc -le "r($float/1.18, 2)"
Gavin Howard

คำตอบ:


31

ฟังก์ชั่นรอบทุบตี:

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

ใช้ในตัวอย่างรหัสของคุณ:

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

ขอให้โชคดี: o)


1
btw ถ้าจำนวนเป็นลบเราต้องใช้-0.5
กุมภ์ Power

ไม่สามารถรับตัวอย่างเพื่อทำงานบนคอนโซลทดสอบ! "การแก้จุดบกพร่อง" เล็ก ๆ น้อย ๆ เปิดเผยว่าสำหรับเช่นผมต้องใช้$2 = 3 echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc))ใจenvก่อน printf! สิ่งนี้จะสอนคุณอีกครั้งว่าการเข้าใจการคัดลอกจากที่อื่นเป็นสิ่งสำคัญเสมอ
ไวยากรณ์

@Aquarius Power ขอบคุณสำหรับแรงบันดาลใจ! ตอนนี้ฉันได้แยกรุ่นของสคริปต์ด้านบนซึ่งจะทำงานกับทั้งจำนวนลบและจำนวนบวก
ไวยากรณ์

นี่คือความซับซ้อนที่ไม่จำเป็นและทำเลขคณิตพิเศษทุกชนิดเพื่อให้ได้คำตอบที่ง่ายกว่าโดย migas และ zuberuber
Adam Katz

@AquariusPower +1 ใช่มันแตกต่างกันสำหรับตัวเลขลบ ในการทดสอบจำนวนลบ (ไม่ใช่จำนวนเต็ม!) ฉันลองใช้เล็กน้อยและตัดสินด้วยif [ echo "$ 1/1" | bc` -gt 0] `- มีวิธีที่สง่างามกว่านี้ในการตรวจสอบ (ยกเว้นการแยกสตริงสำหรับ" - ") หรือไม่?
RobertG

30

วิธีที่ง่ายที่สุด:

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

2
ตามที่ระบุไว้ยังอยู่ในความคิดเห็นด้านบน ( askubuntu.com/a/179949/512213 ) สิ่งนี้จะทำงานอย่างไม่ถูกต้องสำหรับจำนวนลบ แต่น่าเสียดายที่
RobertG

2
@RobertG: ทำไม +0.5การแก้ปัญหานี้ไม่ได้ใช้ ลองprintf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"และคุณจะได้รับและ41.41 -41.41
musiphil

1
เป็นไปได้โดยใช้ไปป์:echo "$float/1.18" | bc -l | xargs printf %.2f
ของหวาน

คำตอบในคำตอบนี้ให้ฉัน: bash: printf: 1.69491525423728813559: invalid numberขึ้นอยู่กับสถานที่
Torsten Bronger

19

การปัดเศษ Bash / awk:

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

หากคุณมีงูหลามคุณสามารถใช้สิ่งนี้:

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

ขอบคุณสำหรับเคล็ดลับ แต่มันไม่ได้แก้สิ่งที่ฉันต้องการ ฉันต้องทำมันโดยใช้เพียงแค่ทุบตี ...
blackedx

1
คำสั่ง python อ่านได้ง่ายขึ้นและดีสำหรับสคริปต์อย่างรวดเร็ว นอกจากนี้ยังรองรับการปัดเศษตัวเลขโดยพลการด้วยการเพิ่มเช่น ", 3" ในฟังก์ชั่นรอบ ขอบคุณ
Elle

echo "$float" |awk '{printf "%.2f", $1/1.18}'จะดำเนินการทางคณิตศาสตร์ที่ร้องขอของคำถามกับการอ่านร้อยอันที่ร้องขอ นั่นคือ "การใช้ bash" มากเท่ากับการbcโทรในคำถาม
Adam Katz

2
สำหรับงูใหญ่คุณก็สามารถใช้สิ่งที่ต้องการที่python -c "print(round($num))" num=4.678923ไม่จำเป็นต้องยุ่งเกี่ยวกับ stdin คุณยังสามารถรอบ n python -c "print(round($num, $n))"ตัวเลขต้องการเพื่อ:
หก

ฉันจัดการเพื่อทำสิ่งที่ค่อนข้างเรียบร้อยด้วยวิธีแก้ปัญหาปัญหาของฉันคือ 0.5 ในกรณีนี้ฉันไม่ได้รับรอบที่ฉันต้องการเสมอดังนั้นฉันจึงได้รับสิ่งต่อไปนี้: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(เมื่อ + จะวนขึ้นและลง - รอบ)
Yaron

4

นี่คือโซลูชัน bc ล้วนๆ กฎการปัดเศษ: ที่ +/- 0.5 ปัดเศษจากศูนย์

ใส่สเกลที่คุณต้องการในรูปแบบ $ result_scale คณิตศาสตร์ของคุณควรเป็นตำแหน่งที่ $ MATH อยู่ในรายการคำสั่ง 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
น่าสนใจมาก! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5 ตามที่อธิบายไว้ที่นี่ :)
Aquarius Power

ฉันเสนอชื่อนี้เพื่อ "คำตอบที่ดีที่สุด"
Marc Tamsky

2

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันมี 'bc' วิธีแก้ปัญหาโดยไม่ต้อง 'ถ้า' หรือสาขา:

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

ใช้เหมือนbcr '2/3' 5หรือbcr '0.666666' 2-> (นิพจน์ตามด้วยสเกล)

เป็นไปได้เพราะใน bc (เช่น C / C ++) อนุญาตให้ผสมนิพจน์เชิงตรรกะในการคำนวณของคุณ นิพจน์((t>0)-(t<0))/2)จะประเมินเป็น +/- 0.5 ขึ้นอยู่กับสัญลักษณ์ของ 't' ดังนั้นจึงใช้ค่าที่เหมาะสมสำหรับการปัดเศษ


นี้ดูเรียบร้อย แต่bcr "5 / 2" 0ผลตอบแทนแทน2 3ฉันพลาดอะไรไปรึเปล่า?
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..."

จริงๆแล้วมันง่าย: ไม่จำเป็นต้องเพิ่มตัวแปร hardcoded "-0.5" สำหรับตัวเลขลบ เราจะคำนวณค่าสัมบูรณ์ของการโต้แย้งและยังคงบวก 0.5 ตามปกติ แต่เนื่องจากเรา (น่าเสียดายที่) ไม่มีabs()ฟังก์ชั่นในตัวที่เราจัดการ (ยกเว้นกรณีที่เราใช้รหัสหนึ่ง) เราก็จะคัดค้านการโต้แย้งถ้ามันเป็นลบ

นอกจากนี้ยังพิสูจน์ว่าเป็นเรื่องยุ่งยากในการทำงานกับความฉลาดทางเป็นพารามิเตอร์ (เนื่องจากวิธีการแก้ปัญหาของฉันฉันจะต้องสามารถเข้าถึงเงินปันผลและตัวหารแยกต่างหาก) นี่คือเหตุผลที่สคริปต์ของฉันมีพารามิเตอร์ที่สามเพิ่มเติม


@muru เกี่ยวกับการแก้ไขล่าสุดของคุณ: herestrings แทนที่จะเป็นecho .. |, เลขคณิตของเชลล์, จุดประสงค์echo $()คืออะไร? นี่เป็นเรื่องง่ายที่จะอธิบาย: สตริงที่นี่ของคุณจะต้องมีไดเรกทอรีที่เขียนได้/tmpเสมอ! และโซลูชันของฉันจะทำงานในสภาพแวดล้อมแบบอ่านอย่างเดียวเช่นเชลล์รูทฉุกเฉินซึ่งปกติ/จะไม่สามารถเขียนได้ตามค่าเริ่มต้น ดังนั้นจึงมีเหตุผลที่ดีว่าทำไมฉันจึงเขียนรหัสด้วยวิธีนี้
ไวยากรณ์

เธอเห็นด้วย แต่เมื่อไหร่echo $()ที่จำเป็น? สิ่งนั้นและการเยื้องก็กระตุ้นการแก้ไขของฉัน
muru

@muru กรณีที่ไม่มีจุดหมายมากที่สุดของecho $()ที่เกินกำหนดที่จะได้รับการแก้ไขในความเป็นจริงสายสุดท้าย, ฮ่า ๆ ขอบคุณสำหรับหัวขึ้น. อย่างน้อยอันนี้ก็ดูดีขึ้นมากในตอนนี้ฉันปฏิเสธไม่ได้ อย่างไรก็ตามฉันชอบตรรกะในเรื่องนี้ สิ่งบูลีนจำนวนมากฟุ่มเฟือยน้อยลง "ถ้า" s (ซึ่งแน่นอนว่าจะทำให้โค้ดระเบิดขึ้น 50 เปอร์เซ็นต์)
ไวยากรณ์

1

ฉันยังคงหาbcคำตอบที่บริสุทธิ์เกี่ยวกับวิธีการปัดเศษเพียงหนึ่งค่าภายในฟังก์ชัน แต่นี่คือbashคำตอบที่แท้จริง:

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

โดยพื้นฐานแล้วสิ่งนี้จะแยกส่วนทศนิยมอย่างรอบคอบคูณทุกอย่างด้วย 100 พันล้าน (10¹⁰ 10**10ในbash) ปรับความแม่นยำและปัดเศษทำการหารจริงหารกลับไปที่ขนาดที่เหมาะสมแล้วใส่ทศนิยมอีกครั้ง

เป็นขั้นเป็นตอน:

embiggen()กำหนดฟังก์ชั่นตัดทอนจำนวนเต็มรูปแบบของการโต้แย้งในการและบันทึกหมายเลขหลังจากจุดใน$int จำนวนของตัวเลขเศษส่วนที่มีการระบุไว้ใน$fraction $precisionคูณคณิตศาสตร์10¹⁰โดยเรียงต่อกันของ$intและ$fractionจากนั้นจึงปรับที่เพื่อให้ตรงกับความแม่นยำ (เช่นembiggen 48.86กลายเป็น10¹⁰× 4886/100 และผลตอบแทน488600000000ซึ่งเป็น 488600000000)

เราต้องการความแม่นยำขั้นสุดท้ายเป็นร้อยส่วนเราจึงคูณจำนวนแรกด้วย 100 เพิ่ม 5 สำหรับการปัดเศษแล้วหารจำนวนที่สอง การมอบหมายนี้$answerทำให้เราได้คำตอบสุดท้ายเป็นร้อยเท่า

ตอนนี้เราต้องเพิ่มจุดทศนิยม เรากำหนด$intค่าใหม่เพื่อ$answerยกเว้นตัวเลขสองหลักสุดท้ายจากนั้นechoจะมีจุดและ$answerไม่รวม$intค่าที่ได้รับการดูแลแล้ว (ไม่ต้องสนใจข้อผิดพลาดของการเน้นไวยากรณ์ที่ทำให้สิ่งนี้ปรากฏเป็นความคิดเห็น)

(Bashism: การยกกำลังไม่ใช่ POSIX ดังนั้นนี่คือ bashism โซลูชัน POSIX ที่บริสุทธิ์จะต้องใช้ลูปเพื่อเพิ่มค่าศูนย์แทนที่จะใช้พลังสิบนอกจากนี้ "embiggen" เป็นคำที่สมบูรณ์แบบที่สุด)


หนึ่งในเหตุผลหลักที่ฉันใช้zshเป็นเชลล์ของฉันก็คือมันรองรับคณิตศาสตร์เลขทศนิยม คำตอบสำหรับคำถามนี้ค่อนข้างตรงไปตรงมาในzsh:

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

(ฉันชอบที่จะเห็นใครบางคนเพิ่มความคิดเห็นในคำตอบนี้ด้วยเคล็ดลับในการเปิดใช้งานการคำนวณเลขทศนิยมbashแต่ฉันค่อนข้างมั่นใจว่าคุณลักษณะดังกล่าวยังไม่มีอยู่)


1

หากคุณมีผลลัพธ์เช่นพิจารณา 2.3747888

สิ่งที่คุณต้องทำคือ:

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

ตัวอย่างนี้ปัดเศษตัวเลขอย่างถูกต้อง:

(2.49999 + 0.5)/1 = 2.99999 

ทศนิยมจะถูกลบออกโดยbcดังนั้นมันจะปัดเศษลงเหลือ 2 ตามที่ควรจะเป็น


1

ฉันต้องคำนวณระยะเวลารวมของการรวบรวมไฟล์เสียง

ดังนั้นฉันต้อง:

A. รับระยะเวลาสำหรับแต่ละไฟล์ ( ไม่แสดง )

B. เพิ่มระยะเวลาทั้งหมด (แต่ละNNN.NNNNNNวินาที (fp) วินาที)

C. แยกชั่วโมงนาทีวินาทีวินาทีย่อย

D. ส่งออกสตริงของ HR: MIN: SEC: เฟรมโดยที่ frame = 1/75 วินาที

(เฟรมมาจากรหัส SMPTE ที่ใช้ในสตูดิโอ)


A: ใช้ffprobeและแยกวิเคราะห์ช่วงเวลาเป็นหมายเลข fp (ไม่แสดง)

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"

1

ต่อไปนี้เป็นเวอร์ชันย่อของสคริปต์ของคุณได้รับการแก้ไขเพื่อให้เอาต์พุตที่คุณต้องการ:

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

โปรดทราบว่าการปัดเศษขึ้นเป็นจำนวนเต็มที่ใกล้เคียงที่สุดเท่ากับการเพิ่ม 0.5 และการปูพื้นหรือการปัดเศษลง (สำหรับตัวเลขบวก)

นอกจากนี้ยังมีการใช้ตัวคูณมาตราส่วนในเวลาที่ใช้งาน ดังนั้น ( bcคำสั่งเหล่านี้คือคุณสามารถวางลงในเทอร์มินัลของคุณ):

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

การใช้งาน bc บริสุทธิ์ตามที่ร้องขอ

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

หากคุณใส่มันลงในไฟล์ที่ชื่อว่า functions.bc คุณสามารถปัดเศษขึ้นด้วย

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

รหัสสำหรับการติดตั้ง bc พบได้ที่http://phodd.net/gnu-bc/code/funcs.bc



0

คุณสามารถใช้ awk โดยไม่ต้องใช้ท่อ:

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

PRICE=<val>ตั้งราคาไว้ล่วงหน้ากับตัวแปรสภาพแวดล้อม

  • แล้วโทร awk - การ$PRICEจะเข้าไปใน awk priceเป็นตัวแปร

  • จากนั้นจะปัดเศษขึ้นเป็น 100 ที่ใกล้ที่สุดด้วย +.005

  • ตัวเลือกการจัดรูปแบบ printf %.2fจำกัด ขนาดเป็นทศนิยมสองตำแหน่ง

หากคุณต้องทำสิ่งนี้มากด้วยวิธีนี้จะมีประสิทธิภาพมากกว่าคำตอบที่เลือก:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.