Bagaimana cara membandingkan dua angka floating point di Bash?

156

Saya berusaha keras untuk membandingkan dua angka floating point dalam skrip bash. Saya harus variabel, misalnya

let num1=3.17648e-22
let num2=1.5

Sekarang, saya hanya ingin melakukan perbandingan sederhana dari dua angka ini:

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

Sayangnya, saya memiliki beberapa masalah dengan perawatan yang tepat dari num1 yang dapat berupa "e-format". :(

Bantuan, petunjuk dipersilahkan!

Jonas
sumber
2
Dengan "e-format" yang saya maksud notasi eksponensial (juga disebut notasi ilmiah)
Jonas

Jawaban:

181

Lebih nyaman

Ini dapat dilakukan dengan lebih nyaman menggunakan konteks numerik Bash:

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

Penjelasan

Memiprat melalui perintah kalkulator dasar bcmengembalikan 1 atau 0.

Opsi -lini setara dengan --mathlib; itu memuat perpustakaan matematika standar.

Menutup seluruh ekspresi antara tanda kurung ganda (( ))akan menerjemahkan nilai-nilai ini menjadi benar atau salah.

Tolong, pastikan bahwa bcpaket kalkulator dasar diinstal.

Ini sama-sama berfungsi untuk mengapung dalam format ilmiah, asalkan menggunakan huruf kapital E, misalnyanum1=3.44E6

Serge Stroobandt
sumber
1
Masalah yang sama dengan stackoverflow.com/questions/8654051/... misalnya $ echo "1.1 + 2e + 02" | bc (standard_in) 1: kesalahan sintaksis
Nemo
1
@MohitArora Tolong, pastikan Anda telah bcmenginstal paket kalkulator.
Serge Stroobandt
1
Saya menerima 0: not foundpernyataan itu if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Stephane
1
Untuk semua orang yang mendapatkan "perintah tidak ditemukan", ingat Anda harus menyertakan ke bcdalam backticks atau $()kemudian ke (( ))... yaitu (( $(bc -l<<<"$a>$b") ))dan tidak (( bc -l<<<"$a>$b" )).
Normadize
@Nemo Tulis angka dalam notasi ilmiah dengan huruf kapital E, dan semua kesalahan sintaks akan hilang.
Serge Stroobandt
100

bash hanya menangani bilangan bulat matematika tetapi Anda dapat menggunakan bcperintah sebagai berikut:

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

Perhatikan bahwa tanda eksponen harus huruf besar

alrusdi
sumber
3
ya, tetapi untuk menyelesaikan perhitungan yang salah diperlukan untuk huruf besar 'e' masuk notasi nomor ilmiah dan gunakan -l flag to bc programm untuk rutin matematika yang telah ditentukan
alrusdi
2
Anda harus menunjukkannya dalam jawaban Anda kemudian, alih-alih hanya memposting solusi yang sangat mirip dan tidak menyebutkan perbedaan penting.
Daniel Persson
4
Ini bukan solusi yang sangat mirip . Solusi Alrusdi menggunakan bcalat ini dan itulah yang akan saya rekomendasikan kepada programmer BASH. BASH adalah bahasa tanpa jenis. Ya, itu bisa melakukan bilangan bulat aritmatika, tetapi untuk floating point Anda harus menggunakan beberapa alat eksternal. SM adalah yang terbaik karena itu dibuat untuk apa.
DejanLekic
8
Karena dia mencoba menggunakannya dalam pernyataan if, saya akan menunjukkan itu. if [$ (... | bc -l) == 1]; lalu ...
Robert Jacobs
27

Lebih baik digunakan awkuntuk matematika non integer. Anda dapat menggunakan fungsi utilitas bash ini:

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

Dan menyebutnya sebagai:

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
anubhava
sumber
2
Saya suka jawaban ini, orang cenderung menghindar dari awk esp pemula, mereka tampaknya berpikir itu lebih sulit daripada yang sebenarnya, saya pikir orang-orang diintimidasi oleh kurung kurawal dan sintaksis campuran bahasa yang tampaknya bahasa (sekilas). Dan karena awk dijamin cukup banyak untuk hadir pada sistem target juga, seperti halnya bc (tidak yakin yang mana, jika ada, yang TIDAK pernah dipasang). Saya suka bash scripting tetapi tidak ada titik apung, bahkan tidak ada 2 desimal (saya kira seseorang bisa menulis bungkus 'palsu' untuk itu), benar-benar menyebalkan ...
osirisgothra
2
Menggunakan awkdan bcdalam skrip shell adalah praktik standar sejak zaman kuno, saya akan mengatakan beberapa fitur tidak pernah ditambahkan ke cangkang karena mereka tersedia dalam awk, bc dan alat Unix lainnya. Tidak perlu untuk kemurnian dalam skrip shell.
piokuc
1
@WanderingMind Salah satu cara untuk melakukannya adalah dengan melewatkan 0 atau 1 exitagar Awk mengkomunikasikan hasilnya kembali ke shell dengan cara yang layak dan dapat dibaca mesin. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... meskipun perhatikan bagaimana kondisinya terbalik (status keluar 0 berarti berhasil ke shell).
tripleee
1
Kenapa saja python. Anda telah perlmenginstal secara default di banyak sistem Linux / Unix .. bahkan phpjuga
anubhava
1
Ini awksolusi lebih kuat dalam kasus saya daripada satu dengan bcyang kembali hasil yang salah untuk alasan saya tidak mendapatkan.
MBR
22

Solusi bash murni untuk membandingkan pelampung tanpa notasi eksponensial, memimpin atau mengekor nol:

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

Urutan operator logis penting . Bagian integer dibandingkan sebagai angka dan bagian fraksional sengaja dibandingkan sebagai string. Variabel dibagi menjadi bilangan bulat dan bagian menggunakan metode ini .

Tidak akan membandingkan pelampung dengan bilangan bulat (tanpa titik).

pengguna
sumber
15

Anda dapat menggunakan awk dikombinasikan dengan bash jika kondisinya, awk akan mencetak 1 atau 0 dan itu akan ditafsirkan oleh jika klausa dengan benar atau salah .

if awk 'BEGIN {print ('$d1' >= '$d2')}'; then
    echo "yes"
else 
    echo "no"
fi
ungalcrys
sumber
Menggunakan awk sangat bagus karena bisa menangani angka floating point, tapi saya pribadi lebih suka synthaxif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt
7

waspadalah saat membandingkan angka yang merupakan versi paket, seperti memeriksa apakah grep 2.20 lebih besar dari versi 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

Saya memecahkan masalah dengan fungsi shell / awk tersebut:

# 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
Elan Ruusamäe
sumber
Pada sistem berbasis Debian, dpkg --compare-versionsseringkali berguna. Ini memiliki logika penuh untuk membandingkan versi paket Debian yang dibangun di dalamnya, yang lebih kompleks dari sekadar x.y.
Neil Mayhew
5

Tentu saja, jika Anda tidak benar-benar membutuhkan aritmatika floating-point, cukup aritmatika pada misalnya nilai dolar di mana selalu ada tepat dua digit desimal, Anda mungkin hanya menjatuhkan titik (secara efektif mengalikan dengan 100) dan membandingkan bilangan bulat yang dihasilkan.

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

Ini jelas mengharuskan Anda untuk memastikan bahwa kedua nilai memiliki jumlah tempat desimal yang sama.

tripleee
sumber
3

Saya menggunakan jawaban dari sini dan menempatkannya dalam suatu fungsi, Anda dapat menggunakannya seperti ini:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Setelah dipanggil, echo $resultakan 1dalam kasus ini, jika tidak 0.

Fungsi:

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}"
}

Atau versi dengan output debug:

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}"
}

Simpan saja fungsi dalam .shfile yang terpisah dan sertakan seperti ini:

. /path/to/the/new-file.sh
Thomas Kekeisen
sumber
3

Saya memposting ini sebagai jawaban untuk https://stackoverflow.com/a/56415379/1745001 ketika sudah ditutup sebagai duplikat dari pertanyaan ini jadi ini dia sebagaimana berlaku di sini juga:

Untuk kesederhanaan dan kejelasan, gunakan awk untuk perhitungan karena ini adalah alat UNIX standar dan kemungkinan besar akan hadir sebagai bc dan lebih mudah untuk bekerja dengan sintaksis.

Untuk pertanyaan ini:

$ 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

dan untuk pertanyaan lain yang ditutup sebagai duplikat dari yang ini:

$ 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...
Ed Morton
sumber
@DudiBoy tidak, itu jelas, sederhana, kode awk portabel atau tidak jelas, shell-tergantung, shell + kode bc.
Ed Morton
3

awkdan alat-alat seperti itu (saya menatap Anda sed...) harus diturunkan ke tong sampah proyek lama, dengan kode yang semua orang terlalu takut untuk disentuh karena ditulis dalam bahasa yang tidak pernah dibaca.

Atau Anda adalah proyek yang relatif jarang yang perlu memprioritaskan optimasi penggunaan CPU daripada optimasi pemeliharaan kode ... dalam hal ini, teruskan.

Namun, jika tidak, mengapa tidak hanya menggunakan sesuatu yang dapat dibaca dan eksplisit, seperti python? Rekan coders dan diri Anda di masa depan akan berterima kasih. Anda dapat menggunakan pythonsebaris dengan bash sama seperti yang lainnya.

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
CivFan
sumber
@ Waniko Versi asli saya agak snarkier.
CivFan
Yang lebih ringkas: gunakan not(...)alih-alih0 if ... else 1
Neil Mayhew
1
Jika Anda membuang awk dan sed (saya melihat Anda CivFan) ke tempat sampah sejarah, Anda adalah administrator sistem yang buruk dan Anda mengetik terlalu banyak kode. (Dan saya suka dan menggunakan Python, jadi ini bukan tentang itu). -1 untuk snarkiness salah tempat. Ada tempat di domain sistem untuk alat-alat itu, Python atau tidak.
Mike S
1
Menariknya, saya mendapatkan Perl yang bagus! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Peasy mudah. Setiap bahasa memiliki tempatnya.
Mike S
1
Jangan menggumpal dengan keanehan sintaksis seds. Tidak seperti python, awk adalah utilitas wajib pada setiap instalasi UNIX dan ekuivalen awk python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"adalah sederhana awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ed Morton
2

Skrip ini dapat membantu tempat saya memeriksa apakah grailsversi yang diinstal lebih besar dari minimum yang diperlukan. Semoga ini bisa membantu.

#!/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
prayagupd
sumber
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
rmil
sumber
2

silakan periksa kode yang diedit di bawah ini: -

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

ini bekerja dengan baik.

Gopika BG
sumber
2

Solusi yang mendukung semua notasi yang mungkin, termasuk notasi ilmiah dengan eksponen huruf besar dan kecil (misalnya, 12.00e4):

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

Gunakan cangkang korn, di bash Anda mungkin harus membandingkan bagian desimal secara terpisah

#!/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
Alan Joseph
sumber
2
masalahnya adalah bahwa banyak distribusi tidak datang dengan ksh diinstal, dan jika skrip Anda akan digunakan oleh orang lain, mereka cenderung tidak suka harus menginstal barang-barang tambahan, terutama ketika itu hanya sebuah skrip yang seharusnya ditulis dalam bash -satu akan berpikir mereka tidak perlu shell LAIN untuk melakukan itu, yang merongrong seluruh alasan menggunakan skrip bash di tempat pertama - yakin kita juga bisa pergi kode di C ++, tapi mengapa?
osirisgothra
Apa distribusi yang datang tanpa menginstal ksh?
piokuc
1
@piokuc misalnya, Ubuntu Desktop & Server. Saya akan mengatakan itu cukup besar ...
Olli
Selain itu, pertanyaan secara khusus menanyakan solusi yang bekerja di bash. Mungkin ada alasan bagus untuk itu. Katakanlah, itu bagian dari aplikasi besar dan memigrasikan semuanya ke ksh tidak layak. Atau itu berjalan pada platform tertanam di mana menginstal shell lain benar-benar masalah.
Olli
1

Menggunakan bashj ( https://sourceforge.net/projects/bashj/ ), sebuah bash mutan dengan dukungan java, Anda cukup menulis (dan ini IS mudah dibaca):

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

Tentu saja bashj bash / java hybridation menawarkan lebih banyak ...

Fil
sumber
0

Bagaimana dengan ini? = 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
Eduardo Lucio
sumber
1
Script Awk seharusnya hanya exit 0melaporkan kebenaran dan exit 1untuk kembali salah; maka Anda dapat menyederhanakan ke elegan luar biasa if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (masih lebih elegan jika Anda merangkum skrip Awk dalam fungsi shell).
tripleee