Bash'de iki kayan noktalı sayı nasıl karşılaştırılır?


156

Bir bash betiği içindeki iki kayan nokta sayısını karşılaştırmak için çok çalışıyorum. Değişkenlerim var, örn.

let num1=3.17648e-22
let num2=1.5

Şimdi, bu iki sayının basit bir karşılaştırmasını yapmak istiyorum:

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

Ne yazık ki, "e-biçiminde" olabilir num1 doğru tedavi ile bazı sorunlar var. :(

Herhangi bir yardım, ipuçları bekliyoruz!


2
"E-format" ile üstel gösterim (bilimsel gösterim de denir) demek istiyorum
Jonas

Yanıtlar:


181

Daha rahat

Bu, Bash'in sayısal bağlamı kullanılarak daha rahat bir şekilde yapılabilir:

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

açıklama

Temel hesap makinesi komutunu kullanarak bcborular 1 veya 0 değerini döndürür.

Seçenek -leşdeğerdir --mathlib; standart matematik kütüphanesini yükler.

Tüm ifadeyi çift parantez içine almak (( )), bu değerleri sırasıyla doğru veya yanlış değerine çevirir.

Lütfen bctemel hesap makinesi paketinin kurulu olduğundan emin olun .

Bu, büyük harf Ekullanılması koşuluyla, bilimsel formatta yüzenler için de aynı şekilde çalışır , örn.num1=3.44E6


1
Stackoverflow.com/questions/8654051/… ile aynı sorun örneğin $ echo "1.1 + 2e + 02" | bc (standard_in) 1: sözdizimi hatası
Nemo

1
@MohitArora Lütfen bchesap makinesi paketinin kurulu olduğundan emin olun .
Serge Stroobandt

1
Ben olsun 0: not founddeyimi ile if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Stephane

1
"Komut bulunamadı" alan herkese, bcya backticks'e ya da $()sonra da (( ))... ie (( $(bc -l<<<"$a>$b") ))ve notun içine koymanız gerektiğini unutmayın (( bc -l<<<"$a>$b" )).
Normadize

@Nemo Sayıları büyük harfle bilimsel gösterimde yazın E, tüm sözdizimi hataları kaybolacaktır.
Serge Stroobandt

100

bash yalnızca tamsayı matematikleri kullanır, ancak bckomutu aşağıdaki gibi kullanabilirsiniz :

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

Üs işaretinin büyük harf olması gerektiğini unutmayın


3
Evet, ancak yanlış hesaplamaları geçici olarak
çözmek

2
o zaman cevabınızda şunu belirtmelisiniz, sadece çok benzer bir çözüm göndermek ve önemli farklılıklardan bahsetmek yerine.
Daniel Persson

4
Bu bir değil çok benzer bir çözüm. Alrusdi'nin çözümü bcaracı kullanıyor ve herhangi bir BASH programcısına tavsiye ederim. BASH tipik bir dildir. Evet, tamsayı aritmetiği yapabilir, ancak kayan nokta için bazı harici araçlar kullanmalısınız. BC en iyisidir çünkü bunun için yapılır.
DejanLekic

8
Bunu bir if ifadesinde kullanmaya çalıştığı için bunu gösterirdim. eğer [$ (... | bc -l) == 1]; sonra ...
Robert Jacobs

27

awkTamsayı olmayan matematik için kullanmak daha iyidir . Bu bash yardımcı işlevini kullanabilirsiniz:

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

Ve şöyle deyin:

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
Bu cevabı beğendim, insanlar awk esp yeni başlayanlardan utangaç olma eğilimindeler, aslında olduğundan daha zor olduğunu düşünüyorlar, sanırım insanlar kıvırcık parantezler ve görünüşte dil karışık sözdizimiyle (bir bakışta) korkutuyorlar. Ve awk'nin hedef sistemde de mevcut olması neredeyse garanti edildiği için, tıpkı bc gibi (hangisinin, eğer varsa, yüklü OLMADIĞINDAN emin değilim). Ben bash betik seviyorum ama hiçbir kayan nokta, hatta yetersiz bir 2 ondalık basamak (birisi bunun için bir 'sahte' sarıcı yazabilirsiniz sanırım), gerçekten sinir bozucu ...
osirisgothra

2
Kabuk betikleri kullanmak awkve bceski zamanlardan beri standart bir uygulamadır, bazı özelliklerin kabuklara asla eklenmediğini söyleyebilirim çünkü awk, bc ve diğer Unix araçlarında mevcuttur. Kabuk betimlerinde saflığa gerek yoktur.
piokuc

1
@WanderingMind Bunu yapmanın bir yolu, Awk'un exitsonucu kabuğa düzgün, makine tarafından okunabilir bir şekilde iletebilmesi için 0 veya 1'i geçmek olacaktır . if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... ancak koşulun nasıl ters çevrildiğine dikkat edin (çıkış durumu 0, kabuğa başarı anlamına gelir).
üçlü

1
Neden sadece python. Sen gelmiş perlbile .. Birçok Linux / Unix sistemleri üzerinde varsayılan olarak yüklü phpda
anubhava

1
Bu awkçözüm benim durumumda bc, alamadığım bir nedenden ötürü yanlış sonuçlar veren çözümden daha sağlam .
MBR

22

Üstel gösterim olmadan, baştaki veya sondaki sıfırlar olmadan yüzenleri karşılaştırmak için saf bash çözümü:

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

Mantıksal operatörlerin sırası önemlidir . Tamsayı kısımlar sayılarla, kesirli kısımlar da kasıtlı olarak dizgilerle karşılaştırılır. Değişkenler tamsayı ve kesirli bölümlere ayrılır. bu yöntem .

Kayan sayıları tamsayılarla (nokta olmadan) karşılaştırmaz.


15

awk koşuluyla birlikte bash komutunu kullanabilirsiniz, awk 1 veya 0 yazdıracak ve bunlar true veya false ile yan tümce olarak yorumlanacaktır .

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

Awk kullanmak harika çünkü kayan nokta sayılarını işleyebiliyor, ama ben şahsen sentaksı tercih ediyorumif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt

7

grep 2.20'nin 2.6 sürümünden büyük olup olmadığını kontrol etmek gibi paket sürümleri olan sayıları karşılaştırırken dikkatli olun:

$ 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

Böyle bir kabuk / awk fonksiyonu ile böyle bir sorunu çözdüm:

# 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

Debian tabanlı bir sistemde dpkg --compare-versionsgenellikle yararlıdır. İçinde yerleşik olan Debian paket sürümlerini karşılaştırmak için tam mantığa sahiptir, bunlar sadece daha karmaşıktır x.y.
Neil Mayhew

5

Tabii ki, gerçekten kayan nokta aritmetiğine ihtiyacınız yoksa, sadece tam olarak iki ondalık hane olan dolar değerlerinde aritmetik, sadece noktayı düşürebilir (100 ile etkili bir şekilde çarparak) ve sonuçtaki tam sayıları karşılaştırabilirsiniz.

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

Bu, her iki değerin aynı sayıda ondalık basamağa sahip olduğundan emin olmanızı gerektirir.


3

Cevapları buradan kullandım ve bir fonksiyona koydum, bunu şu şekilde kullanabilirsiniz:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Bir kez çağrıldığında , aksi takdirde bu durumda echo $resultolacaktır .10

İşlev:

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

Veya hata ayıklama çıktısı olan bir sürüm:

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

Sadece işlevi ayrı bir .shdosyaya kaydedin ve şu şekilde ekleyin:

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

3

Bunu https://stackoverflow.com/a/56415379/1745001 adresine bir yanıt olarak gönderiyordum bu sorunun bir kopyası olarak kapatıldığında , burada da burada olduğu gibi:

Basitlik ve netlik için, hesaplamalar için standart bir UNIX aracı olduğu için awk kullanın ve bc olarak mevcut olması ve sözdizimsel olarak çalışılması çok daha kolay.

Bu soru için:

$ 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

ve bunun bir kopyası olarak kapatılan diğer soru için:

$ 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 hayır, açık, basit, taşınabilir awk kodu veya açık olmayan, karanlık, kabuk bağımlı kabuk + bc kodu.
Ed Morton

3

awk ve bunun gibi araçlar (sana bakıyorum sed ...) eski projelerin çöp kutusuna düşürülmeli, herkesin dokunma korkusuyla yazılmış bir kod asla okunmamış bir dilde yazıldığı için.

Ya da kod bakım optimizasyonu üzerinde CPU kullanım optimizasyonuna öncelik vermesi gereken nispeten nadir bir projesiniz ... bu durumda devam edin.

Değilse, neden sadece okunabilir ve açık bir şey kullanmıyorsunuz python? Kodlayıcı arkadaşlarınız ve gelecekteki benliğiniz size teşekkür edecektir. pythonDiğerleri gibi bash ile satır içi kullanabilirsiniz .

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 Orijinal sürümüm biraz daha keskin.
CivFan

Daha da özlü: not(...)yerine kullanın0 if ... else 1
Neil Mayhew

1
Eğer awk ve sed (size bakıyorum CivFan) tarihin çöp kutusuna düşüyorsanız, berbat bir sistem yöneticisisiniz ve çok fazla kod yazıyorsunuz. (Ve ben Python'u seviyorum ve kullanıyorum, bu yüzden bununla ilgili değil). Yanlış yerleştirilmiş sersemlik için -1. Python ya da no gibi araçlar için sistem etki alanında bir yer var.
Mike S

1
İlginç bir şekilde, ben iyi ol 'Perl ile sona erdi! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Tereyağından kıl çeker gibi. Her dilin bir yeri vardır.
Mike S

1
Seds'in sözdizimsel tuhaflığıyla uğraşmayın. Python'dan farklı olarak awk, her UNIX kurulumunda zorunlu bir yardımcı programdır ve awk eşdeğeri python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"basittir awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ed Morton

2

Bu komut dosyası, yüklü grailssürümün gereken minimum değerden büyük olup olmadığını kontrol ettiğim yerde yardımcı olabilir . Umarım yardımcı olur.

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

lütfen aşağıdaki düzenlenmiş kodu kontrol edin: -

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

bu iyi çalışıyor.


2

Hem büyük hem de küçük harfler üstündeki bilimsel gösterimi içeren tüm olası gösterimleri destekleyen bir çözüm (ör. 12.00e4):

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

1

Korn kabuğu kullanın, bash'da ondalık kısmı ayrı olarak karşılaştırmanız gerekebilir

#!/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
sorun, birçok dağıtımın ksh kurulu olarak gelmemesi ve betiğiniz başkaları tarafından kullanılacaksa, özellikle sadece bash'ta yazılması gereken bir komut dosyası olduğunda, ekstra şeyler yüklemeye gerek duymamalarıdır. -Bunu yapmak için başka bir kabuk gerekmediğini düşünürdüm, bu da ilk etapta bir bash betiği kullanmanın tüm nedenini baltalar - ayrıca C ++ 'da kod yazabileceğimizden emin olabilirsiniz, ama neden?
osirisgothra

Ksh kurulu olmadan gelen dağıtımlar nelerdir?
piokuc

1
@piokuc, örneğin Ubuntu Masaüstü ve Sunucu. Oldukça büyük olanı söyleyebilirim ...
Olli

Ayrıca, soru özellikle bash'da çalışan bir çözüm ister. Bunun için gerçekten iyi nedenler olabilir. Diyelim ki büyük uygulamaların bir parçası ve her şeyi ksh'a taşımak mümkün değil. Ya da başka bir kabuk takmanın gerçekten sorun olduğu gömülü platformda çalışıyor.
Olli

1

Java destekli bir bash mutantı olan bashj ( https://sourceforge.net/projects/bashj/ ) kullanarak , sadece yazıyorsunuz (ve okunması kolay):

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

Tabii ki bashj bash / java hybridation çok daha fazlasını sunuyor ...


0

Buna ne dersin? = 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 betiği sadece exit 0gerçeği rapor etmeli ve exit 1yanlış vermelidir ; o zaman oldukça zarif bir şekilde sadeleştirebilirsiniz if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then...
tripleee
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.