Bagaimana membandingkan dengan angka floating point dalam skrip shell


22

Saya ingin membandingkan dua angka floating point dalam skrip shell. Kode berikut tidak berfungsi:

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

Jawaban:


5

Anda dapat memeriksa bagian bilangan bulat dan pecahan secara terpisah:

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

Seperti dikatakan Fered dalam komentar, itu hanya berfungsi jika kedua angka memiliki bagian fraksional dan kedua bagian fraksional memiliki jumlah digit yang sama. Berikut adalah versi yang berfungsi untuk integer atau fraksional dan operator bash apa pun:

#!/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
Ini tidak dapat diperbaiki tanpa banyak pekerjaan (coba bandingkan 0.5dan 0.06). Anda sebaiknya menggunakan alat yang sudah mengerti notasi desimal.
Gilles 'SO- stop being evil'

Terima kasih Gilles, memperbaruinya agar berfungsi lebih umum daripada versi sebelumnya.
Atau

Perhatikan bahwa dikatakan 1.00000000000000000000000001lebih besar dari 2.
Stéphane Chazelas

Stéphane benar. Ini karena batas bit dalam representasi bash. Tentu saja, jika Anda ingin lebih menderita Anda bisa menggunakan representasi Anda sendiri .... :)
ata

35

Bash tidak mengerti aritmatika floating point. Ini memperlakukan angka yang mengandung titik desimal sebagai string.

Gunakan awk atau bc sebagai gantinya.

#!/bin/bash

min=12.45
val=10.35

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

echo "$min"

Jika Anda berniat melakukan banyak operasi matematika, mungkin lebih baik mengandalkan python atau perl.


12

Anda dapat menggunakan paket num-utils untuk manipulasi sederhana ...

Untuk matematika yang lebih serius, lihat tautan ini ... Ini menjelaskan beberapa opsi, misalnya.

  • R / Rscript (perhitungan statistik dan sistem grafis GNU R)
  • oktaf (sebagian besar kompatibel dengan Matlab)
  • bc (Bahasa kalkulator presisi acak GNU bc)

Contoh dari 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.

Ini adalah bashretas ... Itu menambahkan 0's ke integer untuk membuat perbandingan string kiri-ke-kanan bermakna. Potongan kode khusus ini mengharuskan min dan val benar-benar memiliki titik desimal dan setidaknya satu digit desimal.

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

keluaran:

min=10.35

10

Untuk perhitungan sederhana pada angka floating point (+ - * / dan perbandingan), Anda dapat menggunakan awk.

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

Atau, jika Anda memiliki ksh93 atau zsh (bukan bash), Anda dapat menggunakan aritmatika bawaan shell Anda, yang mendukung angka floating point.

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

Untuk perhitungan floating point lebih lanjut, cari bc . Ini benar-benar bekerja pada angka fixpoint presisi arbitrer.

Untuk mengerjakan tabel angka, lihat R ( contoh ).


6

Gunakan pengurutan angka

Perintah sortmemiliki opsi -g( --general-numeric-sort) yang dapat digunakan untuk perbandingan pada <, "kurang dari" atau> , "lebih besar dari", dengan menemukan minimum atau maksimum.

Contoh-contoh ini menemukan minimum:

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

Mendukung E-Notation

Ini bekerja dengan notasi angka floating point yang cukup umum, seperti dengan E-Notation

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

Perhatikan E-10, membuat angka pertama 0.000000001245, memang kurang dari 10.35.

Dapat dibandingkan dengan tak terbatas

Standar floating point, IEEE754 , mendefinisikan beberapa nilai khusus. Untuk perbandingan ini, yang menarik adalah INFuntuk tak terbatas. Ada juga infinity negatif; Keduanya merupakan nilai yang didefinisikan dengan baik dalam standar.

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

Untuk menemukan penggunaan maksimum sort -grsebagai gantinya sort -g, membalikkan urutan pengurutan:

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

Operasi perbandingan

Untuk mengimplementasikan <perbandingan ("kurang dari"), sehingga dapat digunakan dalam ifdll, bandingkan nilai minimum dengan salah satu dari nilai-nilai tersebut. Jika minimum sama dengan nilai, dibandingkan dengan teks , itu lebih kecil dari nilai lainnya:

$ 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

Tip yang bagus! Saya sangat suka wawasan Anda yang memeriksa a == min(a, b)sama dengan a <= b. Perlu dicatat bahwa ini tidak memeriksa kurang dari itu. Jika Anda ingin melakukan itu, Anda perlu memeriksa a == min(a, b) && a != max(a, b), dengan kata laina <= b and not a >= b
Dave

3

Cukup gunakan ksh( ksh93tepatnya) atau zsh, yang keduanya secara alami mendukung aritmatika floating point:

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

Sunting: Maaf, saya ketinggalan ksh93sudah disarankan. Menjaga jawaban saya hanya untuk memperjelas skrip yang diposting dalam pertanyaan pembuka dapat digunakan tanpa perubahan di luar saklar shell.

Sunting2: Catatan yang ksh93mengharuskan konten variabel konsisten dengan lokal Anda, yaitu dengan lokal Perancis, koma bukan titik harus digunakan:

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

Solusi yang lebih kuat adalah dengan menetapkan lokal di awal skrip untuk memastikan itu akan berfungsi terlepas dari lokal pengguna:

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

Perhatikan bahwa skrip ksh93 di atas hanya berfungsi di lokal tempat pemisah desimal berada .(jadi tidak di separuh dunia tempat pemisah desimal berada ,). zshtidak memiliki masalah itu.
Stéphane Chazelas

Memang, jawaban diedit untuk memperjelas hal itu.
jlliagre

Pengaturan LC_NUMERIC tidak akan berfungsi jika pengguna telah menetapkan LC_ALL, itu juga berarti bahwa angka tidak akan ditampilkan (atau diinput) dalam format yang disukai pengguna. Lihat unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… untuk pendekatan yang berpotensi lebih baik.
Stéphane Chazelas

@ StéphaneChazelas memperbaiki masalah LC_NUMERIC. Dengan sintaks script OP, saya mengasumsikan pemisah yang dipilihnya adalah ..
jlliagre

Ya, tapi itu adalah lokal dari pengguna skrip, bukan lokal dari penulis skrip yang penting. Sebagai penulis skrip, Anda harus memperhitungkan lokalisasi dan efek sampingnya.
Stéphane Chazelas

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

Itu menggunakan dckalkulator untuk smerobek nilai $mindalam register adan dmeningkatkan nilai $valke atas tumpukan eksekusi utamanya. Kemudian lists isinya ake atas tumpukan, pada titik yang terlihat seperti:

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

The <muncul atas dua entri dari tumpukan dan membandingkan mereka. Jadi tumpukan itu terlihat seperti:

${val}

Jika entri atas kurang dari yang kedua ke atas itu mendorong konten ake atas, sehingga tumpukan terlihat seperti:

${min} ${val}

Atau tidak melakukan apa-apa dan tumpukan masih terlihat seperti:

${val} 

Kemudian itu hanya pmerusak entri tumpukan atas.

Jadi untuk masalah Anda:

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

###OUTPUT

12.35

Tapi:

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

###OUTPUT

12.45

0

Mengapa tidak menggunakan yang lama, bagus expr ?

Sintaks contoh:

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

Untuk ekspresi sebenarnya , kode keluar expr adalah 0, dengan string '1' dikirim ke stdout. Membalikkan untuk yang salah ekspresi .

Saya sudah memeriksa ini dengan GNU dan FreeBSD 8 expr.


GNU expr hanya mendukung perbandingan aritmatika pada bilangan bulat. Contoh Anda menggunakan perbandingan leksikografis yang akan gagal pada angka negatif. Misalnya, expr 1.09 '<' -1.1akan mencetak 1dan keluar dengan 0(sukses).
Adrian Günter

0

Untuk memeriksa apakah dua (mungkin pecahan) angka dalam urutan, sortapakah (cukup) portabel:

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

Namun, jika Anda benar-benar ingin menjaga nilai minimum diperbarui, maka Anda tidak perlu if. Sortir angkanya, dan selalu gunakan yang pertama (paling tidak):

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

0

Biasanya saya melakukan hal serupa dengan kode python tertanam:

#!/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
Bisakah Anda mengomentari jawaban Anda dan menambahkan beberapa penjelasan
Romeo Ninov
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.