bash loop เพิ่มขึ้น 0.02


11

ฉันต้องการสร้างห่วงสำหรับทุบตีด้วย 0.02 เป็นส่วนเพิ่มที่ฉันพยายาม

for ((i=4.00;i<5.42;i+=0.02))
do
commands
done

แต่มันไม่ทำงาน


9
Bash ไม่ได้คำนวณเลขทศนิยม
จอร์แดน

1
การเพิ่มสามารถทำได้โดยbcแต่การหยุดที่ 4.52 อาจเป็นเรื่องยุ่งยาก ใช้ข้อเสนอแนะ @roaima มี var เสริมด้วยขั้นตอนที่ 2 และใช้i=$(echo $tmp_var / 100 | bc)
Archemar


5
ปกติคุณไม่ต้องการที่จะใช้ลอยเป็นดัชนีห่วง คุณกำลังสะสมข้อผิดพลาดในการทำซ้ำแต่ละครั้ง
isanae

คำตอบ:


18

การอ่านbash man pageให้ข้อมูลต่อไปนี้:

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

ก่อนอื่นนิพจน์ทางคณิตศาสตร์expr1จะได้รับการประเมินตามกฎที่อธิบายไว้ด้านล่างภายใต้การประเมิน ARITHMETIC [ ... ]

แล้วเราจะได้ส่วนนี้

การประเมินผลทางคณิตศาสตร์

เปลือกช่วยให้การแสดงออกทางคณิตศาสตร์ที่จะได้รับการประเมินภายใต้สถานการณ์บางอย่าง (ดูletและdeclareในตัวคำสั่งและการขยายตัวทางคณิตศาสตร์) การประเมินจะทำในจำนวนเต็มความกว้างคงที่โดยไม่มีการตรวจสอบการล้น [... ]

ดังนั้นจะเห็นได้อย่างชัดเจนว่าคุณไม่สามารถใช้การforวนซ้ำที่มีค่าที่ไม่ใช่จำนวนเต็ม

วิธีแก้ปัญหาหนึ่งอาจจะเพียงแค่คูณส่วนประกอบลูปทั้งหมดของคุณด้วย 100 ซึ่งจะทำให้คุณสามารถใช้งานได้ในภายหลังเช่นนี้

for ((k=400;k<542;k+=2))
do
    i=$(bc <<<"scale=2; $k / 100" )    # when k=402 you get i=4.02, etc.
    ...
done

ฉันคิดว่านี่เป็นทางออกที่ดีที่สุดk=400;k<542;k+=2เพราะหลีกเลี่ยงปัญหาเลขทศนิยมที่อาจเกิดขึ้น
Huygens

1
โปรดทราบว่าสำหรับการวนซ้ำแต่ละครั้งในลูปคุณสร้างไพพ์ (เพื่ออ่านเอาต์พุตของbc) แยกกระบวนการสร้างไฟล์ชั่วคราว (สำหรับที่นี่ - สตริง) ดำเนินการbcในมัน (ซึ่งหมายถึงการโหลดไลบรารีที่ปฏิบัติการและใช้ร่วมกันและ เริ่มต้นพวกเขา) รอมันและทำความสะอาด การวิ่งbcหนึ่งครั้งเพื่อวนรอบจะมีประสิทธิภาพมากขึ้น
Stéphane Chazelas

@ StéphaneChazelasใช่เห็นด้วย แต่ถ้านี่คือคอขวดเราอาจจะเขียนโค้ดในภาษาที่ไม่ถูกต้องอยู่ดี OOI ใดที่ไม่มีประสิทธิภาพน้อยกว่า (!) i=$(bc <<< "scale...")หรือi=$(echo "scale..." | bc)
roaima

1
จากการทดสอบอย่างรวดเร็วของฉัน, รุ่นท่อได้เร็วขึ้นใน zsh (ที่<<<มาจาก) และbash kshโปรดทราบว่าการเปลี่ยนไปใช้เชลล์อื่นbashจะให้ประสิทธิภาพที่ดีกว่าการใช้ไวยากรณ์อื่นอยู่ดี
Stéphane Chazelas

(และส่วนใหญ่ของเปลือกหอยที่สนับสนุน<<<(zsh, mksh, ksh93, Yash) นอกจากนี้ยังสนับสนุนการลอยเลขคณิตจุด ( zsh, ksh93, yash))
Stéphane Chazelas

18

หลีกเลี่ยงการวนซ้ำในเปลือกหอย

หากคุณต้องการทำเลขคณิตให้ใช้awkหรือbc:

awk '
  BEGIN{
    for (i = 4.00; i < 5.42; i+ = 0.02)
      print i
  }'

หรือ

bc << EOF
for (i = 4.00; i < 5.42; i += 0.02)  i
EOF

โปรดทราบว่าawk(ตรงกันข้ามbc) ทำงานร่วมกับการdoubleแสดงหมายเลขลอยตัวโปรเซสเซอร์ของคุณ(น่าจะเป็นประเภทIEEE 754 ) ด้วยเหตุนี้เนื่องจากตัวเลขเหล่านั้นเป็นการประมาณแบบไบนารีของตัวเลขทศนิยมเหล่านั้นคุณอาจประหลาดใจ:

$ gawk 'BEGIN{for (i=0; i<=0.3; i+=0.1) print i}'
0
0.1
0.2

หากคุณเพิ่มOFMT="%.17g"คุณจะเห็นเหตุผลของการหายไป0.3:

$ gawk 'BEGIN{OFMT="%.17g"; for (i=0; i<=0.5; i+=0.1) print i}'
0
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.5

bc ความแม่นยำตามอำเภอใจจึงไม่มีปัญหาเช่นนี้

โปรดทราบว่าโดยค่าเริ่มต้น (เว้นแต่คุณจะปรับเปลี่ยนรูปแบบผลลัพธ์ด้วยOFMTหรือใช้printfกับข้อกำหนดรูปแบบที่ชัดเจน) awkใช้%.6gสำหรับการแสดงหมายเลขจุดลอยตัวดังนั้นจะเปลี่ยนเป็น 1e6 และสูงกว่าสำหรับจำนวนจุดลอยตัวเหนือ 1,000,000 และตัดส่วนเศษส่วนสำหรับตัวเลขสูง (100000.02 จะแสดงเป็น 100,000)

หากคุณไม่ต้องการจริงๆที่จะใช้ห่วงเปลือกเพราะเช่นคุณต้องการที่จะเรียกใช้คำสั่งที่เฉพาะเจาะจงสำหรับการย้ำห่วงว่าแต่ละอย่างใดอย่างหนึ่งใช้เปลือกที่มีจุดลอยสนับสนุนทางคณิตศาสตร์เช่นzsh, yashหรือksh93หรือสร้างรายการของค่ากับหนึ่งในคำสั่งดังกล่าวข้างต้น (หรือseqถ้ามี) และวนลูปมากกว่าเอาท์พุท

ชอบ:

unset -v IFS # configure split+glob for default word splitting
for i in $(seq 4 0.02 5.42); do
  something with "$i"
done

หรือ:

seq 4 0.02 5.42 | while IFS= read i; do
  something with "$i"
done

หากคุณไม่ จำกัด จำนวนจุดลอยตัวโปรเซสเซอร์ของคุณให้seqจัดการข้อผิดพลาดที่เกิดขึ้นจากการประมาณจุดลอยตัวอย่างนุ่มนวลกว่าawkรุ่นที่กล่าวมาข้างต้น

หากคุณไม่มีseq(คำสั่ง GNU) คุณสามารถสร้างสิ่งที่เชื่อถือได้มากขึ้นเป็นฟังก์ชั่นเช่น:

seq() { # args: first increment last
  bc << EOF
    for (i = $1; i <= $3; i += $2) i
EOF
}

seq 100000000001 0.000000001 100000000001.000000005ที่จะทำงานได้ดีขึ้นสำหรับสิ่งที่ต้องการ โปรดทราบว่าการมีตัวเลขที่มีความแม่นยำสูงโดยพลการจะไม่ช่วยอะไรมากหากเราจะส่งต่อไปยังคำสั่งที่ไม่สนับสนุน


ฉันขอขอบคุณที่ใช้ awk! +1
Pandya

ทำไมคุณต้องทำunset IFSในตัวอย่างแรก
user1717828

@ user1717828 อย่างยอดเยี่ยมด้วยการแบ่ง + glob invocation เราต้องการแยกอักขระบรรทัดใหม่ เราสามารถทำสิ่งนั้นได้ด้วยIFS=$'\n'แต่มันไม่ได้ผลในเปลือกหอยทั้งหมด หรือIFS='<a-litteral-newline-here>'แต่นั่นไม่ชัดเจนมาก หรือเราสามารถแบ่งคำแทน (เว้นวรรคแท็บขึ้นบรรทัดใหม่) เหมือนกับที่คุณได้รับด้วยค่าเริ่มต้นของ $ IFS หรือถ้าคุณยกเลิกการตั้งค่า IFS และทำงานได้ที่นี่
Stéphane Chazelas

@ user1717828: เราไม่จำเป็นต้องยุ่งIFSเพราะเรารู้ว่าseqผลลัพธ์ของมันไม่มีช่องว่างที่เราต้องหลีกเลี่ยงการแยก ส่วนใหญ่จะมีเพื่อให้แน่ใจว่าคุณตระหนักว่าตัวอย่างนี้ขึ้นอยู่กับIFSซึ่งอาจมีความสำคัญสำหรับคำสั่งสร้างรายการที่แตกต่างกัน
Peter Cordes

1
@PeterCordes มันอยู่ที่นั่นเราจึงไม่จำเป็นต้องทำการสันนิษฐานว่า IFS นั้นถูกตั้งค่าไว้ล่วงหน้า
Stéphane Chazelas


0

ตามที่คนอื่นแนะนำคุณสามารถใช้ bc:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.