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
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
Respostas:
Você pode verificar separadamente as partes inteiras e fracionárias:
#!/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, ele funciona apenas se ambos os números tiverem partes fracionárias e ambas as partes fracionais tiverem o mesmo número de dígitos. Aqui está uma versão que funciona para números inteiros ou fracionários e para 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
1.00000000000000000000000001
é maior que 2
.
O Bash não entende a aritmética de ponto flutuante. Ele trata números que contêm um ponto decimal como seqüências de caracteres.
Use awk ou bc.
#!/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.
Você pode usar o pacote num-utils para manipulações simples ...
Para matemática mais séria, consulte este link ... Ele descreve várias opções, por exemplo.
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 zeros iniciais ao número inteiro para tornar significativa uma comparação da esquerda para a direita. Esse trecho de código específico requer que min e val 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
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), poderá usar a aritmética interna do seu shell, que suporta números de ponto flutuante.
if ((min>val)); then ((val=min)); fi
Para cálculos mais avançados de ponto flutuante, procure bc . Na verdade, ele funciona em números de pontos de correção de precisão arbitrária.
Para trabalhar em tabelas de números, procure R ( exemplo ).
O comando sort
possui 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 o máximo.
Estes exemplos estão encontrando o mínimo:
$ printf '12.45\n10.35\n' | sort -g | head -1
10.35
Ele funciona com notação bastante geral de números de ponto flutuante, como na Notação E
$ 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
.
O padrão de ponto flutuante, IEEE754 , define alguns valores especiais. Para essas comparações, as interessantes são INF
para o infinito. Há 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 em sort -gr
vez de sort -g
, invertendo a ordem de classificação:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
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 , é 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
a == min(a, b)
é o mesmo que a <= b
. Vale a pena notar que isso não verifica estritamente menos do que no entanto. Se você quiser fazer isso, precisará verificar a == min(a, b) && a != max(a, b)
, em outras palavrasa <= b and not a >= b
Basta usar ksh
(com ksh93
precisão) 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 esclarecer o script postado na pergunta de abertura pode ser usada sem alterações fora do switch do shell.
Edit2: Observe que ksh93
exige que o conteúdo da variável seja consistente com o seu código do idioma, ou seja, com um código do idioma francês, uma vírgula em vez de um ponto deve ser usada:
...
min=12,45
val=10,35
...
Uma solução mais robusta é definir o código do idioma no início do script para garantir que ele funcione independentemente do código do idioma do usuário:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(portanto, não na metade do mundo onde o separador decimal está ,
). zsh
não tem esse problema.
LC_ALL
, isso também significa que os números não serão exibidos (ou inseridos) no formato preferido do usuário. Consulte unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… para obter uma abordagem potencialmente melhor.
.
.
min=$(echo "${min}sa ${val}d la <a p" | dc)
Que usa a dc
calculadora para s
rasgou o valor $min
no registo a
e d
uplicates o valor $val
para o topo de sua principal pilha de execução. Em seguida, l
coloca o conteúdo a
no topo da pilha, quando é parecido com:
${min} ${val} ${val}
O <
aparece no topo duas entradas fora da pilha e compara-los. Portanto, a pilha se parece com:
${val}
Se a entrada superior for menor que a segunda para cima, ela enviará o conteúdo a
para a parte superior, para que a pilha se pareça com:
${min} ${val}
Senão, ele não faz nada e a pilha ainda se parece com:
${val}
Depois, apenas p
cria 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 que não usar velho, bom expr
?
Exemplo de sintaxe:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Para expressões verdadeiras , o código de saída expr é 0, com a string '1' enviada para stdout. Inverter para expressões falsas .
Eu verifiquei isso com o GNU e FreeBSD 8 expr.
expr 1.09 '<' -1.1
imprimirá 1
e sairá com 0
(sucesso).
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 deseja manter um valor mínimo atualizado, não precisa de 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
Normalmente, faço coisas semelhantes com o 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
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
0.5
e0.06
). É melhor usar uma ferramenta que já entenda a notação decimal.