จะเปรียบเทียบตัวเลขทศนิยมสองตัวใน Bash ได้อย่างไร


156

ฉันพยายามอย่างหนักเพื่อเปรียบเทียบตัวเลขทศนิยมสองจำนวนภายในสคริปต์ทุบตี ฉันต้องเปลี่ยนตัวแปรเช่น

let num1=3.17648e-22
let num2=1.5

ตอนนี้ฉันแค่ต้องการเปรียบเทียบตัวเลขสองตัวนี้ง่ายๆ:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

น่าเสียดายที่ฉันมีปัญหากับการใช้ num1 ที่เหมาะสมซึ่งอาจเป็น "รูปแบบ e" :(

ความช่วยเหลือใด ๆ คำแนะนำยินดีต้อนรับ!


2
ด้วย "รูปแบบ e" ฉันหมายถึงสัญกรณ์เอ็กซ์โปเนนเชียล (เรียกอีกอย่างว่าสัญกรณ์ทางวิทยาศาสตร์)
Jonas

คำตอบ:


181

สะดวกยิ่งขึ้น

สิ่งนี้สามารถทำได้สะดวกยิ่งขึ้นโดยใช้บริบทตัวเลขของ Bash:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  
fi

คำอธิบาย

การไพพ์ผ่านคำสั่งเครื่องคิดเลขพื้นฐานbcจะส่งคืน 1 หรือ 0

ตัวเลือก-lเทียบเท่ากับ--mathlib; มันโหลดไลบรารีคณิตศาสตร์มาตรฐาน

การล้อมรอบการแสดงออกทั้งหมดระหว่างวงเล็บคู่(( ))จะแปลค่าเหล่านี้เป็นจริงหรือเท็จตามลำดับ

โปรดตรวจสอบให้แน่ใจว่าbcได้ติดตั้งแพคเกจเครื่องคิดเลขพื้นฐานแล้ว

สิ่งนี้ใช้ได้กับรูปแบบทางวิทยาศาสตร์อย่างเท่าเทียมกันหากใช้ตัวอักษรพิมพ์Eใหญ่เช่นnum1=3.44E6


1
ปัญหาเดียวกันกับstackoverflow.com/questions/8654051/…เช่น $ echo "1.1 + 2e + 02" | bc (standard_in) 1: ข้อผิดพลาดทางไวยากรณ์
Nemo

1
@MohitArora กรุณาตรวจสอบให้แน่ใจว่าคุณได้bcติดตั้งแพ็คเกจเครื่องคิดเลข
Serge Stroobandt

1
ฉันจะได้รับด้วยกับคำสั่ง0: not found if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then
Stephane

1
ทุกคนที่ได้รับคำสั่ง "ไม่พบ" จำไว้ว่าคุณต้องใส่bcลงไปใน backticks หรือ$()แล้วเข้าไป(( ))... คือไม่(( $(bc -l<<<"$a>$b") )) (( bc -l<<<"$a>$b" ))
ธรรมดา

@Nemo เขียนตัวเลขในเครื่องหมายทางวิทยาศาสตร์ด้วยตัวพิมพ์Eใหญ่และข้อผิดพลาดทางไวยากรณ์ทั้งหมดจะหายไป
Serge Stroobandt

100

bash จัดการคณิตศาสตร์จำนวนเต็มเท่านั้น แต่คุณสามารถใช้bcคำสั่งดังต่อไปนี้:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

โปรดทราบว่าเครื่องหมายตัวแทนต้องเป็นตัวพิมพ์ใหญ่


3
ใช่ แต่เพื่อหลีกเลี่ยงการคำนวณที่ไม่ถูกต้องจำเป็นต้องใช้ตัวพิมพ์ใหญ่ 'e' ในเครื่องหมายทางวิทยาศาสตร์และใช้แฟ
ล็ก

2
คุณควรชี้ให้เห็นว่าในคำตอบของคุณแล้วแทนที่จะโพสต์โซลูชันที่คล้ายกันมากและไม่พูดถึงความแตกต่างที่สำคัญ
Daniel Persson

4
มันไม่ใช่ทางออกที่คล้ายกันมาก โซลูชันของ Alrusdi ใช้bcเครื่องมือและนั่นคือสิ่งที่ฉันอยากจะแนะนำให้โปรแกรมเมอร์ BASH BASH เป็นภาษาที่ไม่พิมพ์ ใช่มันสามารถคำนวณเลขจำนวนเต็มได้ แต่สำหรับเลขทศนิยมคุณต้องใช้เครื่องมือภายนอก BC นั้นดีที่สุดเพราะนั่นคือสิ่งที่มันทำขึ้นมา
DejanLekic

8
เนื่องจากเขาพยายามใช้มันในคำสั่ง if หากฉันจะแสดงให้เห็นว่า ถ้า [$ (... | bc -l) == 1]; จากนั้น ...
Robert Jacobs

27

จะดีกว่าที่จะใช้awkสำหรับคณิตศาสตร์ที่ไม่ใช่จำนวนเต็ม คุณสามารถใช้ฟังก์ชันยูทิลิตี้ bash นี้:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

และเรียกมันว่า:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679

2
ฉันชอบคำตอบนี้คนมักจะขี้อายจากผู้เริ่มต้น awk พวกเขาดูเหมือนจะคิดว่ามันยากกว่าที่เป็นจริงฉันคิดว่าผู้คนถูกข่มขู่ด้วยวงเล็บปีกกาและไวยากรณ์ผสมภาษาที่ดูเหมือน (ทันที) และเนื่องจาก awk รับประกันได้ค่อนข้างมากว่าจะปรากฏบนระบบเป้าหมายเช่นกันเช่นเดียวกับ bc (ไม่แน่ใจว่าจะติดตั้งอันไหนถ้ามีจะไม่ติดตั้ง) ฉันรักการเขียนสคริปต์ bash แต่ไม่มีจุดลอยตัวแม้แต่ตำแหน่งทศนิยม 2 ตำแหน่งที่น้อย (ฉันเดาว่าใครบางคนสามารถเขียนเสื้อคลุม 'ปลอม' สำหรับสิ่งนั้น) เป็นเรื่องที่น่ารำคาญจริงๆ ...
osirisgothra

2
การใช้awkและbcในเชลล์สคริปต์เป็นวิธีปฏิบัติมาตรฐานมาตั้งแต่สมัยโบราณฉันจะบอกว่าคุณสมบัติบางอย่างไม่เคยถูกเพิ่มลงในเชลล์เพราะมันมีอยู่ใน awk, bc และเครื่องมือ Unix อื่น ๆ ไม่จำเป็นต้องมีความบริสุทธิ์ในเชลล์สคริปต์
piokuc

1
@WanderingMind วิธีหนึ่งในการทำเช่นนั้นคือการส่งค่า 0 หรือ 1 ไปexitที่ Awk สื่อสารผลลัพธ์กลับไปยังเชลล์ในวิธีที่เหมาะสมและสามารถอ่านได้ด้วยเครื่อง if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... แม้ว่าโปรดทราบว่าเงื่อนไขจะกลับด้าน (สถานะออก 0 หมายถึงความสำเร็จของเชลล์)
tripleee

1
pythonทำไมเพียงแค่ คุณได้perlติดตั้งตามค่าเริ่มต้นในระบบ Linux / Unix หลายระบบ .. และphpยัง
anubhava

1
นี้awkการแก้ปัญหามีประสิทธิภาพมากขึ้นในกรณีของฉันมากกว่าหนึ่งกับbcว่าผลตอบแทนที่ไม่ถูกต้องด้วยเหตุผลที่ผมไม่ได้รับ
MBR

22

สารละลายทุบตีบริสุทธิ์สำหรับการเปรียบเทียบลอยโดยไม่ต้องสัญกรณ์ชี้แจงนำหน้าหรือศูนย์ต่อท้าย:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

การสั่งซื้อสินค้าของผู้ประกอบการลอจิคัลเรื่อง ชิ้นส่วนจำนวนเต็มจะถูกนำมาเปรียบเทียบกันเป็นตัวเลขและส่วนที่เป็นเศษส่วนจะถูกเปรียบเทียบโดยเจตนาว่าเป็นสตริง ตัวแปรจะถูกแบ่งออกเป็นส่วนจำนวนเต็มและเศษส่วนโดยใช้วิธีนี้นี้

จะไม่เปรียบเทียบลอยกับจำนวนเต็ม (ไม่มีจุด)


15

คุณสามารถใช้ awk รวมกับทุบตีถ้าสภาพ awk จะพิมพ์1หรือ0และผู้ที่จะได้รับการตีความโดยถ้าข้อกับความจริงหรือเท็จ

if awk 'BEGIN {print ('$d1' >= '$d2')}'; then
    echo "yes"
else 
    echo "no"
fi

การใช้ awk นั้นยอดเยี่ยมเพราะมันสามารถจัดการกับจำนวนจุดลอยตัว แต่โดยส่วนตัวแล้วผมชอบ synthaxif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt

7

ระวังเมื่อเปรียบเทียบตัวเลขที่เป็นเวอร์ชันแพ็คเกจเช่นการตรวจสอบว่า grep 2.20 มากกว่าเวอร์ชัน 2.6:

$ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
YES

ฉันแก้ไขปัญหาดังกล่าวด้วยฟังก์ชัน shell / awk:

# get version of GNU tool
toolversion() {
    local prog="$1" operator="$2" value="$3" version

    version=$($prog --version | awk '{print $NF; exit}')

    awk -vv1="$version" -vv2="$value" 'BEGIN {
        split(v1, a, /\./); split(v2, b, /\./);
        if (a[1] == b[1]) {
            exit (a[2] '$operator' b[2]) ? 0 : 1
        }
        else {
            exit (a[1] '$operator' b[1]) ? 0 : 1
        }
    }'
}

if toolversion grep '>=' 2.6; then
   # do something awesome
fi

บนระบบที่ใช้เดเบียนdpkg --compare-versionsมักมีประโยชน์ มันมีตรรกะเต็มรูปแบบสำหรับการเปรียบเทียบ Debian x.yรุ่นแพคเกจในตัวมันซึ่งมีความซับซ้อนมากกว่าเพียงแค่
Neil Mayhew

5

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

if [[ $((10#${num1/.})) < $((10#${num2/.})) ]]; then
    ...

สิ่งนี้จะทำให้คุณต้องแน่ใจว่าค่าทั้งสองมีจำนวนทศนิยมเท่ากัน


3

ฉันใช้คำตอบจากที่นี่และวางไว้ในฟังก์ชั่นคุณสามารถใช้มันเช่นนี้:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

เมื่อเรียกว่าecho $resultจะเป็น1ในกรณีนี้มิฉะนั้น0ในกรณีนี้มิฉะนั้น

ฟังก์ชั่น:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    __FUNCTION_RETURN="${result}"
}

หรือเวอร์ชันที่มีเอาต์พุตดีบัก:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    echo "... is_first_floating_number_bigger: result is: ${result}"

    if [ "$result" -eq 0 ]; then
        echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
    else
        echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
    fi

    __FUNCTION_RETURN="${result}"
}

เพียงบันทึกฟังก์ชั่นใน.shไฟล์แยกและรวมไว้เช่นนี้

. /path/to/the/new-file.sh

3

ฉันโพสต์สิ่งนี้เป็นคำตอบไปที่https://stackoverflow.com/a/56415379/1745001เมื่อปิดเป็นคำถามซ้ำดังนั้นนี่จึงเป็นเพราะมันใช้ได้ที่นี่ด้วย:

เพื่อความง่ายและชัดเจนเพียงใช้ awk สำหรับการคำนวณเนื่องจากเป็นเครื่องมือ UNIX มาตรฐานและดังนั้นจึงน่าจะมีอยู่ในรูปแบบ bc และง่ายต่อการทำงานกับ syntactically

สำหรับคำถามนี้:

$ cat tst.sh
#!/bin/bash

num1=3.17648e-22
num2=1.5

awk -v num1="$num1" -v num2="$num2" '
BEGIN {
    print "num1", (num1 < num2 ? "<" : ">="), "num2"
}
'

$ ./tst.sh
num1 < num2

และสำหรับคำถามอื่น ๆ ที่ถูกปิดเป็นสำเนาของคำถามนี้:

$ cat tst.sh
#!/bin/bash

read -p "Operator: " operator
read -p "First number: " ch1
read -p "Second number: " ch2

awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
BEGIN {
    if ( ( op == "/" ) && ( ch2 == 0 ) ) {
        print "Nope..."
    }
    else {
        print ch1 '"$operator"' ch2
    }
}
'

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 2
2.25

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 0
Nope...

@DudiBoy ไม่มันชัดเจนง่ายรหัส awk แบบพกพาหรือไม่ชัดเจนชัดเจนเปลือกขึ้นอยู่กับรหัสเชลล์ + bc
Ed Morton

3

awk และเครื่องมือเช่นนี้ (ฉันจ้องมองที่คุณ sed ... ) ควรผลักไสไล่ส่งไปยังถังขยะของโครงการเก่าด้วยรหัสที่ทุกคนกลัวเกินกว่าจะสัมผัสได้เพราะมันเขียนด้วยภาษาที่อ่านไม่ออก

หรือคุณเป็นโครงการที่ค่อนข้างหายากซึ่งจำเป็นต้องจัดลำดับความสำคัญการเพิ่มประสิทธิภาพการใช้งาน CPU ผ่านการปรับแต่งการบำรุงรักษารหัส ... ในกรณีนี้

ถ้าไม่ได้ แต่ทำไมไม่แทนที่จะใช้เพียงบางสิ่งบางอย่างที่สามารถอ่านได้อย่างชัดเจนเช่นpython? เพื่อนโค้ดของคุณและตัวตนในอนาคตจะขอบคุณ คุณสามารถใช้pythonแบบอินไลน์ด้วยการทุบตีเหมือนคนอื่น ๆ ทั้งหมด

num1=3.17648E-22
num2=1.5
if python -c "exit(0 if $num1 < $num2 else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi

@Witiko รุ่นเดิมของฉันค่อนข้างน่าสะอิดสะเอียน
CivFan

รวบรัดมากขึ้น: ใช้not(...)แทน0 if ... else 1
นีลเมย์ฮิว

1
หากคุณกำลังผลักไส awk และ sed (ฉันมองคุณ CivFan) ไปที่ถังขยะของประวัติศาสตร์คุณเป็นผู้ดูแลระบบหมัดและคุณพิมพ์รหัสมากเกินไป (และฉันชอบและใช้ Python ดังนั้นมันจึงไม่เกี่ยวกับเรื่องนั้น) -1 สำหรับ snarkiness หายไป มีสถานที่ในโดเมนระบบสำหรับเครื่องมือเหล่านั้นคือ Python หรือไม่
Mike S

1
ที่น่าสนใจฉันจบลงด้วยความดี ol 'Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. peasy ง่าย ๆ ทุกภาษามีที่อยู่ของมัน
Mike S

1
ไม่ต้องตะลึงงันด้วยความเยือกเย็นของ syntacic ซึ่งแตกต่างจากงูหลาม awk เป็นสาธารณูปโภคที่บังคับใช้กับทุกการติดตั้ง UNIX และเทียบเท่า awk ของเป็นเพียงpython -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)" awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}"
Ed Morton

2

สคริปต์นี้อาจช่วยให้ฉันตรวจสอบว่าgrailsรุ่นที่ติดตั้งมากกว่าที่จำเป็น หวังว่ามันจะช่วย

#!/bin/bash                                                                                         

min=1.4                                                                                             
current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`                         

if [ 1 -eq `echo "${current} < ${min}" | bc` ]                                                          
then                                                                                                
    echo "yo, you have older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi

2
num1=0.555
num2=2.555


if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
       echo "$num1 is greater then $num2"
else
       echo "$num2 is greater then $num1"
fi

2

โปรดตรวจสอบรหัสที่แก้ไขด้านล่าง: -

#!/bin/bash

export num1=(3.17648*e-22)
export num2=1.5

st=$((`echo "$num1 < $num2"| bc`))
if [ $st -eq 1 ]
  then
    echo -e "$num1 < $num2"
  else
    echo -e "$num1 >= $num2"
fi

มันใช้งานได้ดี


2

วิธีการแก้ปัญหาที่รองรับเครื่องหมายที่เป็นไปได้ทั้งหมดรวมถึงสัญกรณ์ทางวิทยาศาสตร์ที่มีทั้งตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก (เช่น12.00e4):

if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
then
    echo "$value1 is below $value2"
fi 

1

ใช้ korn shell ใน bash คุณอาจต้องเปรียบเทียบส่วนทศนิยมแยกกัน

#!/bin/ksh
X=0.2
Y=0.2
echo $X
echo $Y

if [[ $X -lt $Y ]]
then
     echo "X is less than Y"
elif [[ $X -gt $Y ]]
then
     echo "X is greater than Y"
elif [[ $X -eq $Y ]]
then
     echo "X is equal to Y"
fi

2
ปัญหาคือการกระจายจำนวนมากไม่ได้มาพร้อมกับการติดตั้ง ksh และหากผู้อื่นจะใช้สคริปต์ของคุณพวกเขามักจะไม่ต้องการติดตั้งสิ่งพิเศษโดยเฉพาะอย่างยิ่งเมื่อมันเป็นเพียงสคริปต์ที่ควรจะเขียนในทุบตี - ใครจะคิดว่าพวกเขาไม่ต้องการเชลล์อีกอันเพื่อทำเช่นนั้นซึ่งทำลายเหตุผลทั้งหมดของการใช้สคริปต์ทุบตีในตอนแรก - เพื่อให้แน่ใจว่าเราสามารถใช้รหัสใน C ++ ได้ แต่ทำไม?
osirisgothra

ดิสทริบิวชันใดที่มาโดยไม่ติดตั้ง ksh?
piokuc

1
@piokuc เช่น Ubuntu Desktop & Server ผมจะบอกว่ามันเป็นหนึ่งในที่สำคัญสวย ...
Olli

นอกจากนี้คำถามยังถามถึงวิธีการแก้ปัญหาที่ใช้งานได้ดี อาจมีเหตุผลที่ดีจริงๆ สมมติว่าเป็นส่วนหนึ่งของแอปพลิเคชันขนาดใหญ่และการย้ายทุกอย่างไปยัง ksh นั้นไม่สามารถทำได้ หรือมันทำงานบนแพลตฟอร์มแบบฝังที่ติดตั้งเชลล์ตัวอื่นเป็นปัญหาจริงๆ
Olli

1

ใช้ bashj ( https://sourceforge.net/projects/bashj/ ) bash กลายพันธุ์ด้วยการสนับสนุน java คุณเพียงแค่เขียน (และง่ายต่อการอ่าน):

#!/usr/bin/bashj

#!java
static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}

#!bashj
num1=3.17648e-22
num2=1.5
comp=j.doubleCompare($num1,$num2)
if [ $comp == 0 ] ; then echo "Equal" ; fi
if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi

แน่นอน bashj bash / java hybridation มีอะไรมากกว่านี้ ...


0

แล้วเรื่องนี้ล่ะ = D

VAL_TO_CHECK="1.00001"
if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
    echo "$VAL_TO_CHECK >= 1"
else
    echo "$VAL_TO_CHECK < 1"
fi

1
สคริปต์ Awk ควรexit 0รายงานความจริงและexit 1กลับเท็จเท่านั้น จากนั้นคุณสามารถลดความซับซ้อนของสง่างามที่น่าทึ่งif awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (ยังคงสง่างามยิ่งขึ้นหากคุณใส่แค็ปซูลสคริปต์ Awk ในฟังก์ชันเชลล์)
tripleee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.