Como comparar com o número de ponto flutuante em um script de shell

20

Eu quero comparar dois números de ponto flutuante em um script de shell. O código a seguir não está funcionando:

#!/bin/bash   
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo $min 
    
por RIchard Williams 16.11.2011 / 13:47

11 respostas

3

Você pode verificar separadamente as partes inteira e fracionária:

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

Como diz Fered nos comentários, só funciona se ambos os números tiverem partes fracionárias e ambas as partes fracionárias tiverem o mesmo número de dígitos. Aqui está uma versão que funciona para inteiro ou fracional e qualquer operador 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
    
por 16.11.2011 / 16:32
32

Bash não entende aritmética de ponto flutuante. Ele trata números que contêm um ponto decimal como strings.

Use awk ou bc no lugar.

#!/bin/bash

min=12.45
val=10.35

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

echo "$min"

Se você pretende fazer muitas operações matemáticas, provavelmente é melhor confiar em python ou perl.

    
por 16.11.2011 / 14:18
11

Você pode usar o pacote num-utils para manipulações simples ...

Para matemática mais séria, veja este link ... Descreve várias opções, por exemplo.

  • R / Rscript (sistema de computação estatística e gráficos GNU R)
  • oitava (principalmente compatível com Matlab)
  • bc (A linguagem de calculadora de precisão arbitrária do GNU bc)

Um exemplo de 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.

Aqui está um bash hack ... Ele adiciona 0s iniciais ao inteiro para fazer uma comparação entre aspas da esquerda para a direita significativa. Esta parte específica do código requer que tanto min e val realmente tenham um ponto decimal e pelo menos um dígito decimal.

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

saída:

min=10.35
    
por 16.11.2011 / 14:07
9

Para cálculos simples em números de ponto flutuante (+ - * / e comparações), você pode usar o awk.

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

Ou, se você tiver ksh93 ou zsh (não bash), você pode usar a aritmética embutida do seu shell, que suporta números de ponto flutuante.

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

Para cálculos de ponto flutuante mais avançados, procure bc . Ele realmente funciona em números de pontos de fixação de precisão arbitrária.

Para trabalhar em tabelas de números, procure R ( exemplo ).

    
por 17.11.2011 / 01:09
5

Usar classificação numérica

O comando sort tem uma opção -g ( --general-numeric-sort ) que pode ser usada para comparações em < , "menor que" ou > , "maior que", localizando o mínimo ou máximo .

Estes exemplos estão encontrando o mínimo:

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

Suporta E-Notação

Funciona com uma notação geral de números de ponto flutuante, como com a notação eletrônica

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

Observe o E-10 , tornando o primeiro número 0.000000001245 , na verdade, menor que 10.35 .

Pode ser comparado ao infinito

O padrão de ponto flutuante, IEEE754 , define alguns valores especiais. Para essas comparações, as interessantes são INF para o infinito. Existe também o infinito negativo; Ambos são valores bem definidos no padrão.

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

Para encontrar o uso máximo sort -gr em vez de sort -g , invertendo a ordem de classificação:

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

Operação de comparação

Para implementar a comparação < ("menor que"), para que possa ser usada em if etc, compare o mínimo com um dos valores. Se o mínimo for igual ao valor, comparado como texto , será menor que o outro valor:

$ 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
    
por 14.10.2014 / 15:40
3

Use apenas ksh ( ksh93 precisamente) ou zsh , que suportam nativamente aritmética de ponto flutuante:

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

Edit: Desculpe, eu perdi ksh93 já foi sugerido. Manter minha resposta apenas para deixar claro que o script postado na pergunta de abertura pode ser usado sem nenhuma alteração fora do switch da shell.

Editar2: observe que ksh93 exige que o conteúdo da variável seja consistente com sua localidade, ou seja, com uma localidade francesa, uma vírgula em vez de um ponto deve ser usada:

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

Uma solução mais robusta é definir a localidade no início do script para garantir que funcione independentemente da localidade do usuário:

...
export LC_ALL=C
min=12.45
val=10.35
...
    
por 20.11.2011 / 22:42
1
min=$(echo "${min}sa ${val}d la <a p" | dc)

Isso usa a calculadora dc para s para obter o valor de $min no registro a e d aplica o valor de $val no topo de sua pilha de execução principal. Então, l é o conteúdo de a no topo da pilha, no ponto em que parece:

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

O < exibe as duas primeiras entradas da pilha e compara-as. Então, a pilha se parece com:

${val}

Se a entrada superior for menor do que a segunda para o topo, o conteúdo de a é colocado no topo, então a pilha se parece com:

${min} ${val}

Senão não faz nada e a pilha ainda se parece com:

${val} 

Em seguida, basta p inserir a entrada da pilha superior.

Então, para o seu problema:

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

###OUTPUT

12.35

Mas:

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

###OUTPUT

12.45
    
por 14.06.2014 / 20:30
0

Por que não usar o bom e velho expr ?

Exemplo de sintaxe:

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

Para expressões true , o código de saída expr é 0, com a string '1' enviada para a stdout. Reverter para expressões falsas .

Eu verifiquei isso com o GNU e o FreeBSD 8 expr.

    
por 03.08.2015 / 07:33
0

Para verificar se dois números (possivelmente fracionários) estão em ordem, sort é (razoavelmente) portátil:

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

No entanto, se você realmente quiser manter um valor mínimo atualizado, não será necessário um if . Classifique os números e use sempre o primeiro (menos):

min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
    
por 15.07.2016 / 17:52
0

Geralmente faço coisas semelhantes com código python incorporado:

#!/bin/sh

min=12.45
val=10.35

python - $min $val<<EOF
if ($min > $val):
        print $min
else: 
        print $val
EOF
    
por 26.09.2016 / 23:12
-1
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
    
por 03.02.2017 / 07:30