Bir kabuk komut dosyasındaki kayan nokta sayılarıyla nasıl karşılaştırılır


22

Bir kabuk komut dosyasındaki iki kayan nokta sayısını karşılaştırmak istiyorum. Aşağıdaki kod çalışmıyor:

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

Yanıtlar:


5

Tamsayılı ve kesirli kısımları ayrı ayrı kontrol edebilirsiniz:

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

Fered yorumlarda söylediği gibi, sadece her iki sayı da kesirli parçalara sahipse ve her iki kesimli parça aynı sayıdaki rakamlara sahipse çalışır. İşte tamsayı veya kesirli ve bash operatörü için çalışan bir sürüm:

#!/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
Çok fazla iş olmadan bu düzeltilemez (karşılaştırmayı deneyin 0.5ve 0.06). Ondalık gösterimi zaten anlayan bir araç kullansanız iyi edersiniz.
Gilles 'SO- kötülük' kas

Teşekkürler Gilles, önceki sürümden daha genel olarak çalışması için güncellendi.
ata

Bunun 1.00000000000000000000000001daha büyük olduğunu söylediğini unutmayın 2.
Stéphane Chazelas

Stéphane haklı. Bu, bash'ın sayı temsilindeki bit sınırları nedeniyledir. Tabii ki, daha fazla acı çekmek istersen, kendi temsilciliğini kullanabilirsin .... :)
ata

35

Bash kayan nokta aritmetiğini anlamıyor. Ondalık nokta içeren sayıları, dizge olarak kabul eder.

Bunun yerine awk veya bc kullanın.

#!/bin/bash

min=12.45
val=10.35

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

echo "$min"

Çok fazla matematik işlemi yapmak niyetindeyseniz, python veya perl'e güvenmek muhtemelen daha iyidir.


12

Basit manipülasyonlar için num-utils paketini kullanabilirsiniz ...

Daha ciddi matematik için, bu bağlantıya bakın ... Birkaç seçenek, örn.

  • R / Rscript (GNU R istatistiksel hesaplama ve grafik sistemi)
  • oktav (çoğunlukla Matlab uyumlu)
  • bc (GNU bc isteğe bağlı hassas hesap makinesi dili)

Bir örnek 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.

İşte bir bashkesmek ... Soldan sağa bir karşılaştırma anlamlı hale getirmek için tamsayıya lider 0 ekler. Bu kod, belirli parçası hem gerektirir dk ve val aslında bir ondalık noktası ve en az bir ondalık rakam var.

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

çıktı:

min=10.35

10

Kayan nokta sayıları ile ilgili basit hesaplamalar için (+ - * / ve karşılaştırmalar), awk kullanabilirsiniz.

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

Veya, ksh93 veya zsh (bash değil) varsa, kabuğunuzun kayan nokta sayılarını destekleyen yerleşik aritmetiğini kullanabilirsiniz.

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

Daha gelişmiş kayan nokta hesaplamaları için, bc'ye bakınız . Aslında isteğe bağlı kesinti sayıları üzerinde çalışır.

Sayı tabloları üzerinde çalışmak için, R'ye ( örnek ) bakın.


6

Sayısal sıralama kullan

Komut sortbir seçenek vardır -g( --general-numeric-sortüzerinde karşılaştırmalar için kullanılabilir) <"az" ya da, >"daha büyük", minimum veya maksimum bularak.

Bu örnekler asgari buluyor:

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

E-Notasyon Desteği

E-Notasyonda olduğu gibi kayan nokta sayılarının oldukça genel gösterimi ile çalışır.

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

Not, E-10ilk sayıyı yapan 0.000000001245, aslında daha az 10.35.

Sonsuza kadar karşılaştırabilir

Kayan nokta standardı, IEEE754 , bazı özel değerleri tanımlar. Bu karşılaştırmalar için ilginç olanlar INFsonsuzluk içindir. Negatif sonsuzluk da var; Her ikisi de standartta iyi tanımlanmış değerlerdir.

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

Bunun sort -gryerine maksimum kullanımı bulmak için sort -g, sıralama düzenini tersine çevirerek:

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

Karşılaştırma işlemi

Uygulamak için <onu kullanılabilir, böylece ( "az") karşılaştırma ifvb değerlerden birine minimum karşılaştırın. Asgari değere eşitse, metinle karşılaştırıldığında , diğer değerden azsa:

$ 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

İyi bahşiş! Kontrol a == min(a, b)etmenin aynı olduğu görüşünü gerçekten beğendim a <= b. Bunun olsa da kesinlikle daha azını kontrol etmediğini belirtmekte fayda var. Bunu yapmak istiyorsanız, için kontrol edilmesi gereken a == min(a, b) && a != max(a, b)Başka bir deyişle,a <= b and not a >= b
Dave

3

Sadece ksh( ksh93kesin) veya zshdoğal olarak kayan noktalı aritmetik özelliğini destekleyen kullanın:

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

Düzenleme: Üzgünüm, kaçırdım ksh93zaten önerildi. Cevabımı sadece açıklığa kavuşturmak için yazılan betiği açıklığa kavuşturmak için kabuk anahtarının dışında hiçbir değişiklik yapılmadan kullanılabilir.

Düzen2: ksh93Değişken içeriğin yerel ayarınızla tutarlı olmasını gerektiren, yani bir Fransız yerel ayarıyla, nokta yerine virgül kullanılması gerektiğini unutmayın:

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

Kullanıcının yerel ayarından bağımsız olarak çalışacağından emin olmak için komut dosyasını başında yerel ayarı ayarlamak daha güçlü bir çözümdür:

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

Yukarıdaki ksh93 betiğinin yalnızca ondalık ayırıcının bulunduğu yerlerde çalıştığını unutmayın .(bu nedenle ondalık ayırıcının bulunduğu dünyanın yarısında değil ,). zshBu konuda bir sorun yok.
Stéphane Chazelas

Aslında, bu noktaya açıklık getirmek için cevap düzenlendi.
jlliagre

LC_NUMERIC ayarı, kullanıcı ayarlamışsa çalışmaz LC_ALL, bu da kullanıcının tercih edilen biçimde sayıların gösterilmeyeceği (veya girileceği) anlamına gelmez. Potansiyel olarak daha iyi bir yaklaşım için unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… adresine bakın .
Stéphane Chazelas

@ StéphaneChazelas, LC_NUMERIC sorununu çözdü. OP betiği sözdizimi göz önüne alındığında, onun tercih edilen ayırıcısının .yine de olduğunu farz ediyorum .
jlliagre

Evet, ancak önemli olan komut dosyası yazarının yerel ayarı değil, komut dosyası kullanıcısının yerel ayarı. Bir senaryo yazarı olarak, yerelleştirmeyi ve yan etkilerini dikkate almalısınız.
Stéphane Chazelas

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

Kullandığı dciçin hesap sdeğeri yırtıp $minkayıttaki ave ddeğerini uplicates $valana yürütme yığının üstüne. Daha sonra l, içeriğini ayığının üstüne, hangi noktada olduğu gibi gösterir:

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

<Yığının kapalı üst iki giriş çıkar ve karşılaştırır. Böylece yığın şöyle görünür:

${val}

Üst giriş ikinci üstten küçükse içeriği üste doğru iter a, böylece yığın şöyle görünür:

${min} ${val}

Başka hiçbir şey yapmaz ve yığın hala şöyle görünür:

${val} 

Sonra sadece püst yığın girişini işaret eder.

Demek problemin için:

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

###OUTPUT

12.35

Fakat:

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

###OUTPUT

12.45

0

Neden eski, iyi kullanmıyorsun expr?

Örnek sözdizimi:

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

İçin gerçek ifadeler, expr çıkış kodu Stdout'a gönderilen dize '1' ile, 0'dır. Yanlış ifadeler için ters .

Bunu GNU ve FreeBSD 8 expr ile kontrol ettim.


GNU expr yalnızca tam sayılarda aritmetik karşılaştırmayı destekler. Örnekte, negatif sayılar üzerinde başarısız olacak sözlük bilgisi karşılaştırması kullanılır. Örneğin, expr 1.09 '<' -1.1basar 1ve çıkar 0(başarı ile).
Adrian Günter

0

İki (muhtemelen kesirli) sayının sıralı olup olmadığını kontrol etmek sortiçin (makul şekilde) taşınabilir:

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

Ancak, asgari bir değeri güncel tutmak istiyorsanız, o zaman bir ihtiyacınız olmaz if. Numaraları sıralayın ve daima ilk (en az) olanı kullanın:

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

0

Genellikle ben gömülü python koduyla benzer şeyler yaparım:

#!/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
Lütfen cevabınızı yorumlayabilir ve bazı açıklamalar ekleyebilir misiniz
Romeo Ninov
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.