วิธีเปรียบเทียบกับตัวเลขทศนิยมในเชลล์สคริปต์


22

ฉันต้องการเปรียบเทียบตัวเลขทศนิยมสองตัวในเชลล์สคริปต์ รหัสต่อไปนี้ใช้งานไม่ได้:

#!/bin/bash   
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo $min 

คำตอบ:


5

คุณสามารถตรวจสอบส่วนจำนวนเต็มและเศษส่วนแยกต่างหาก:

#!/bin/bash
min=12.45
val=12.35    
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then    
    min=$val
fi
echo $min

ดังที่ fered บอกไว้ในความคิดเห็นมันใช้งานได้ก็ต่อเมื่อตัวเลขทั้งสองมีส่วนที่เป็นเศษส่วนและส่วนที่เป็นเศษส่วนทั้งสองมีจำนวนหลักเท่ากัน นี่คือรุ่นที่ใช้งานได้สำหรับจำนวนเต็มหรือเศษส่วนและตัวดำเนินการ bash ใด ๆ :

#!/bin/bash
shopt -s extglob
fcomp() {
    local oldIFS="$IFS" op=$2 x y digitx digity
    IFS='.' x=( ${1##+([0]|[-]|[+])}) y=( ${3##+([0]|[-]|[+])}) IFS="$oldIFS"
    while [[ "${x[1]}${y[1]}" =~ [^0] ]]; do
        digitx=${x[1]:0:1} digity=${y[1]:0:1}
        (( x[0] = x[0] * 10 + ${digitx:-0} , y[0] = y[0] * 10 + ${digity:-0} ))
        x[1]=${x[1]:1} y[1]=${y[1]:1} 
    done
    [[ ${1:0:1} == '-' ]] && (( x[0] *= -1 ))
    [[ ${3:0:1} == '-' ]] && (( y[0] *= -1 ))
    (( ${x:-0} $op ${y:-0} ))
}

for op in '==' '!=' '>' '<' '<=' '>='; do
    fcomp $1 $op $2 && echo "$1 $op $2"
done

4
สิ่งนี้ไม่สามารถแก้ไขได้หากไม่มีงานจำนวนมาก (ลองเปรียบเทียบ0.5และ0.06) คุณควรใช้เครื่องมือที่เข้าใจสัญลักษณ์ทศนิยมอยู่แล้ว
Gilles 'หยุดความชั่วร้าย'

ขอบคุณ Gilles อัปเดตให้ใช้งานได้ดีกว่ารุ่นก่อนหน้า
ata

โปรดทราบว่ามันบอกว่ามีค่ามากกว่า1.00000000000000000000000001 2
Stéphane Chazelas

Stéphaneถูกต้อง มันเป็นเพราะขีด จำกัด บิตในการแทนจำนวนของ bash แน่นอนถ้าคุณต้องการความทุกข์ทรมานมากขึ้นคุณสามารถใช้การเป็นตัวแทนของคุณเอง .... :)
ata

35

Bash ไม่เข้าใจคณิตศาสตร์เลขทศนิยม มันถือว่าตัวเลขที่มีจุดทศนิยมเป็นสตริง

ใช้ awk หรือ bc แทน

#!/bin/bash

min=12.45
val=10.35

if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then  
    min=${val}
fi

echo "$min"

หากคุณต้องการดำเนินการทางคณิตศาสตร์จำนวนมากมันน่าจะดีกว่าถ้าคุณใช้ python หรือ perl


12

คุณสามารถใช้แพ็คเกจnum-utils สำหรับการปรับเปลี่ยนอย่างง่าย ๆ ...

สำหรับคณิตศาสตร์ที่รุนแรงยิ่งขึ้นให้ดูที่ลิงค์นี้ ...ซึ่งจะอธิบายตัวเลือกต่างๆเช่น

  • R / Rscript (การคำนวณทางสถิติ GNU R และระบบกราฟิก)
  • ระดับแปดเสียง (ส่วนใหญ่เข้ากันได้กับ Matlab)
  • bc (ภาษาเครื่องคำนวณความแม่นยำโดยพลการของ GNU bc)

ตัวอย่างของ numprocess

echo "123.456" | numprocess /+33.267,%2.33777/
# 67.0395291239087  

A programs for dealing with numbers from the command line

The 'num-utils' are a set of programs for dealing with numbers from the
Unix command line. Much like the other Unix command line utilities like
grep, awk, sort, cut, etc. these utilities work on data from both
standard in and data from files.

Includes these programs:
 * numaverage: A program for calculating the average of numbers.
 * numbound: Finds the boundary numbers (min and max) of input.
 * numinterval: Shows the numeric intervals between each number in a sequence.
 * numnormalize: Normalizes a set of numbers between 0 and 1 by default.
 * numgrep: Like normal grep, but for sets of numbers.
 * numprocess: Do mathematical operations on numbers.
 * numsum: Add up all the numbers.
 * numrandom: Generate a random number from a given expression.
 * numrange: Generate a set of numbers in a range expression.
 * numround: Round each number according to its value.

นี่คือbashแฮ็ค ... มันเพิ่ม 0 นำไปสู่จำนวนเต็มเพื่อทำการเปรียบเทียบสตริงจากซ้ายไปขวาที่มีความหมาย รหัสชิ้นส่วนนี้ต้องการให้ทั้ง minและvalมีจุดทศนิยมและอย่างน้อยหนึ่งหลัก

min=12.45
val=10.35

MIN=0; VAL=1 # named array indexes, for clarity
IFS=.; tmp=($min $val); unset IFS 
tmp=($(printf -- "%09d.%s\n" ${tmp[@]}))
[[ ${tmp[VAL]} < ${tmp[MIN]} ]] && min=$val
echo min=$min

เอาท์พุท:

min=10.35

10

สำหรับการคำนวณอย่างง่าย ๆ เกี่ยวกับจำนวนจุดลอยตัว (+ - * / และการเปรียบเทียบ) คุณสามารถใช้ awk

min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')

หรือถ้าคุณมี ksh93 หรือ zsh (ไม่ใช่ bash) คุณสามารถใช้เลขคณิตในตัวของเชลล์ซึ่งรองรับตัวเลขทศนิยม

if ((min>val)); then ((val=min)); fi

สำหรับการคำนวณจุดลอยสูงขึ้นมองขึ้นBC ใช้งานได้จริงกับหมายเลข fixpoint ที่แม่นยำโดยพลการ

ในการทำงานกับตารางตัวเลขให้ค้นหา R ( ตัวอย่าง )


6

ใช้การเรียงลำดับตัวเลข

คำสั่งsortมีตัวเลือก-g( --general-numeric-sort) ที่สามารถใช้สำหรับการเปรียบเทียบ< "น้อยกว่า" หรือ>"ใหญ่กว่า" โดยค้นหาต่ำสุดหรือสูงสุด

ตัวอย่างเหล่านี้กำลังค้นหาขั้นต่ำ:

$ printf '12.45\n10.35\n' | sort -g | head -1
10.35

รองรับ E-Notation

มันทำงานกับสัญกรณ์ทั่วไปของตัวเลขจุดลอยตัวเช่นเดียวกับ E-Notation

$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10

หมายเหตุE-10ทำให้หมายเลขแรกแน่นอนน้อยกว่า0.00000000124510.35

สามารถเปรียบเทียบกับอนันต์

มาตรฐานจุดลอยตัวIEEE754กำหนดค่าพิเศษบางอย่าง สำหรับการเปรียบเทียบเหล่านี้สิ่งที่น่าสนใจINFสำหรับอินฟินิตี้ นอกจากนี้ยังมีอินฟินิตี้ลบ ทั้งสองเป็นค่าที่กำหนดไว้อย่างดีในมาตรฐาน

$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF

ในการค้นหาการใช้งานสูงสุดsort -grแทนการsort -gย้อนกลับลำดับการจัดเรียง:

$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45

การดำเนินการเปรียบเทียบ

ในการใช้การเปรียบเทียบ<("น้อยกว่า") ดังนั้นจึงสามารถใช้ในifฯลฯ เปรียบเทียบค่าต่ำสุดกับค่าใดค่าหนึ่ง หากค่าต่ำสุดเท่ากับค่าเมื่อเปรียบเทียบกับข้อความจะน้อยกว่าค่าอื่น ๆ :

$ a=12.45; b=10.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
1
$ a=12.45; b=100.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?                                              
0

เคล็ดลับดี! ผมชอบความเข้าใจของคุณที่ตรวจสอบเป็นเช่นเดียวกับa == min(a, b) a <= bมันน่าสังเกตว่านี่ไม่ได้ตรวจสอบอย่างเคร่งครัดน้อยกว่า หากคุณต้องการทำเช่นนั้นคุณต้องตรวจสอบa == min(a, b) && a != max(a, b)ในคำอื่น ๆa <= b and not a >= b
เดฟ

3

เพียงแค่ใช้ksh( ksh93แม่นยำ) หรือzshซึ่งทั้งสองสนับสนุนการคำนวณเลขทศนิยม:

$ cat test.ksh
#!/bin/ksh 
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo "$min"
$ ./test.ksh
10.35

แก้ไข: ขออภัยฉันพลาดksh93ไปแล้วแนะนำ การรักษาคำตอบของฉันเพียงเพื่อให้ชัดเจนสคริปต์ที่โพสต์ในคำถามเปิดสามารถใช้โดยไม่มีการเปลี่ยนแปลงนอกเชลล์สวิทช์

แก้ไข 2: โปรดทราบว่าksh93ต้องใช้เนื้อหาตัวแปรให้สอดคล้องกับสถานที่ของคุณเช่นกับสถานที่ภาษาฝรั่งเศสต้องใช้เครื่องหมายจุลภาคแทนที่จะเป็นจุด:

...
min=12,45
val=10,35
...

โซลูชันที่มีประสิทธิภาพมากขึ้นคือการตั้งค่าภาษาที่จุดเริ่มต้นของสคริปต์เพื่อให้แน่ใจว่ามันจะทำงานโดยไม่คำนึงถึงภาษาของผู้ใช้:

...
export LC_ALL=C
min=12.45
val=10.35
...

โปรดทราบว่าสคริปต์ ksh93 ด้านบนใช้งานได้ในโลแคลที่มีตัวคั่นทศนิยมเท่านั้น.(ไม่ใช่ครึ่งโลกที่มีตัวคั่นทศนิยม,) zshไม่มีปัญหานั้น
Stéphane Chazelas

แน่นอนตอบแก้ไขเพื่อชี้แจงจุดนั้น
jlliagre

การตั้งค่า LC_NUMERIC จะไม่ทำงานหากผู้ใช้ตั้งค่าLC_ALLซึ่งหมายความว่าจะไม่แสดงตัวเลข (หรืออินพุต) ในรูปแบบที่ผู้ใช้ต้องการ ดูunix.stackexchange.com/questions/87745/what-does-lc-all-c-do/…สำหรับแนวทางที่ดีกว่า
Stéphane Chazelas

@ StéphaneChazelasแก้ไขปัญหา LC_NUMERIC แล้ว ด้วยไวยากรณ์ของสคริปต์ OP ฉันถือว่าสมมติว่าตัวคั่นที่เขาต้องการอยู่.แล้ว
jlliagre

ใช่ แต่เป็นภาษาของผู้ใช้สคริปต์ไม่ใช่ภาษาของผู้เขียนสคริปต์ที่สำคัญ ในฐานะผู้เขียนสคริปต์คุณควรคำนึงถึงการแปลและผลข้างเคียงของมันด้วย
Stéphane Chazelas

1
min=$(echo "${min}sa ${val}d la <a p" | dc)

ที่ใช้dcเครื่องคิดเลขเพื่อsฉีกค่าสำหรับ$minในการลงทะเบียนaและduplicates ค่าของ$valลงบนด้านบนของกองดำเนินการหลักของมัน จากนั้นlists เนื้อหาของaลงบนด้านบนของสแต็ก ณ จุดที่มีลักษณะ:

${min} ${val} ${val}

<ปรากฏด้านบนสองรายการออกจากสแต็คและเปรียบเทียบพวกเขา ดังนั้นสแต็กจะมีลักษณะดังนี้:

${val}

หากรายการบนสุดมีค่าน้อยกว่ารายการที่สองขึ้นไปรายการจะส่งเนื้อหาของaไปยังด้านบนดังนั้นสแต็กจะมีลักษณะดังนี้:

${min} ${val}

ไม่เช่นนั้นจะทำอะไรและสแต็กยังคงมีลักษณะ:

${val} 

จากนั้นมันก็จะpลบรายการสแต็คด้านบน

ดังนั้นสำหรับปัญหาของคุณ:

min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.35

แต่:

min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.45

0

ทำไมไม่ใช้ของเก่าดีexprล่ะ?

ไวยากรณ์ตัวอย่าง:

if expr 1.09 '>' 1.1 1>/dev/null; then
    echo 'not greater'
fi

สำหรับนิพจน์ที่แท้จริงรหัสออกจาก expr คือ 0 โดยส่งสตริง '1' ไปที่ stdout กลับด้านสำหรับนิพจน์เท็จ

ฉันได้ตรวจสอบกับ GNU และ FreeBSD 8 expr แล้ว


GNU expr รองรับการเปรียบเทียบทางคณิตศาสตร์กับจำนวนเต็มเท่านั้น ตัวอย่างของคุณใช้การเปรียบเทียบโดยย่อซึ่งจะล้มเหลวในจำนวนลบ ตัวอย่างเช่นexpr 1.09 '<' -1.1จะพิมพ์1และออกด้วย0(สำเร็จ)
Adrian Günter

0

ในการตรวจสอบว่ามีสองหมายเลข (อาจเป็นเศษส่วน) ในลำดับหรือsortไม่เป็นแบบพกพา (พอสมควร):

min=12.45
val=12.55
if { echo $min ; echo $val ; } | sort -n -c 2>/dev/null
then
  echo min is smallest
else
  echo val is smallest
fi

ifแต่ถ้าคุณต้องการจริงที่จะให้ค่าต่ำสุดปรับปรุงแล้วคุณไม่จำเป็นต้อง จัดเรียงตัวเลขและใช้หนึ่ง (อย่างน้อย) แรก:

min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest

0

ฉันมักจะทำสิ่งที่คล้ายกับรหัสหลามฝังตัว:

#!/bin/sh

min=12.45
val=10.35

python - $min $val<<EOF
if ($min > $val):
        print $min
else: 
        print $val
EOF

-1
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13

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