Quiero comparar dos números de coma flotante en un script de shell. El siguiente código no funciona:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Quiero comparar dos números de coma flotante en un script de shell. El siguiente código no funciona:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Respuestas:
Puede verificar por separado las partes enteras y fraccionarias:
#!/bin/bash
min=12.45
val=12.35
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then
min=$val
fi
echo $min
Como dice Fered en los comentarios, funciona solo si ambos números tienen partes fraccionarias y ambas partes fraccionarias tienen el mismo número de dígitos. Aquí hay una versión que funciona para enteros o fraccionarios y cualquier 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
es mayor que 2
.
Bash no entiende la aritmética de coma flotante. Trata los números que contienen un punto decimal como cadenas.
Use awk o bc en su lugar.
#!/bin/bash
min=12.45
val=10.35
if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then
min=${val}
fi
echo "$min"
Si tiene la intención de hacer muchas operaciones matemáticas, probablemente sea mejor confiar en python o perl.
Puede usar el paquete num-utils para manipulaciones simples ...
Para matemáticas más serias, vea este enlace ... Describe varias opciones, por ejemplo.
Un ejemplo 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.
Aquí hay un bash
truco ... Agrega los primeros 0 al entero para hacer que una comparación de cadena de izquierda a derecha sea significativa. Este código particular requiere que tanto min como val tengan un punto decimal y al menos un 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
salida:
min=10.35
Para cálculos simples sobre números de coma flotante (+ - * / y comparaciones), puede usar awk.
min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')
O, si tiene ksh93 o zsh (no bash), puede usar la aritmética integrada de su shell, que admite números de coma flotante.
if ((min>val)); then ((val=min)); fi
Para cálculos de coma flotante más avanzados, busque bc . Realmente funciona en números de punto de fijación de precisión arbitraria.
El comando sort
tiene una opción -g
( --general-numeric-sort
) que se puede usar para comparaciones en <
"menor que" o >
"mayor que", encontrando el mínimo o el máximo.
Estos ejemplos son encontrar el mínimo:
$ printf '12.45\n10.35\n' | sort -g | head -1
10.35
Funciona con notación bastante general de números de coma flotante, como con la notación E
$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10
Tenga en cuenta que E-10
, haciendo el primer número 0.000000001245
, de hecho menor que 10.35
.
El estándar de coma flotante, IEEE754 , define algunos valores especiales. Para estas comparaciones, las interesantes son INF
para el infinito. También está el infinito negativo; Ambos son valores bien definidos en el estándar.
$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF
Para encontrar el uso máximo sort -gr
lugar de sort -g
invertir el orden de clasificación:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
Para implementar la <
comparación ("menor que"), de modo que pueda usarse en if
etc., compare el mínimo con uno de los valores. Si el mínimo es igual al valor, comparado como texto , es menor que el otro 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)
es lo mismo que a <= b
. Vale la pena señalar que esto no verifica estrictamente menos que sin embargo. Si desea hacer eso, debe verificar a == min(a, b) && a != max(a, b)
, en otras palabrasa <= b and not a >= b
Simplemente use ksh
( ksh93
precisamente) o zsh
, que ambos admiten de forma nativa la aritmética de coma flotante:
$ cat test.ksh
#!/bin/ksh
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo "$min"
$ ./test.ksh
10.35
Editar: Lo siento, me perdí ksh93
ya fue sugerido. Mantener mi respuesta solo para dejar en claro que el script publicado en la pregunta de apertura se puede usar sin cambios fuera del interruptor de shell.
Edit2: tenga en cuenta que ksh93
requiere que el contenido variable sea coherente con su configuración regional, es decir, con una configuración regional en francés, se debe utilizar una coma en lugar de un punto:
...
min=12,45
val=10,35
...
Una solución más robusta es establecer la configuración regional al comienzo del script para asegurarse de que funcionará independientemente de la configuración regional del usuario:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(por lo tanto, no en la mitad del mundo donde está el separador decimal ,
). zsh
No tiene ese problema.
LC_ALL
, eso también significa que los números no se mostrarán (o ingresarán) en el formato preferido del usuario. Ver unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… para un enfoque potencialmente mejor.
.
todos modos.
min=$(echo "${min}sa ${val}d la <a p" | dc)
Que utiliza la dc
calculadora para s
rasgaron el valor $min
en el registro a
y d
uplicates el valor de $val
en la parte superior de su pila de ejecución principal. Luego l
coloca el contenido a
en la parte superior de la pila, en cuyo punto se ve así:
${min} ${val} ${val}
La <
hace estallar la parte superior dos entradas fuera de la pila y los compara. Entonces la pila se ve así:
${val}
Si la entrada superior era menor que la segunda hacia arriba, empuja el contenido a
hacia arriba, por lo que la pila se ve así:
${min} ${val}
De lo contrario, no hace nada y la pila todavía se ve así:
${val}
Luego simplemente p
borra la entrada de la pila superior.
Entonces para tu problema:
min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.35
Pero:
min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.45
¿Por qué no usar viejo, bueno expr
?
Ejemplo de sintaxis:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Para expresiones verdaderas , el código de salida expr es 0, con la cadena '1' enviada a stdout. Invertir para expresiones falsas .
He comprobado esto con GNU y FreeBSD 8 expr.
expr 1.09 '<' -1.1
imprimirá 1
y saldrá con 0
(éxito).
Para verificar si dos números (posiblemente fraccionarios) están en orden, sort
es (razonablemente) 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
Sin embargo, si realmente desea mantener actualizado un valor mínimo, entonces no necesita un if
. Ordena los números y usa siempre el primero (mínimo):
min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
Por lo general, hago cosas similares con el código de Python incrustado:
#!/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
y0.06
). Será mejor que use una herramienta que ya entienda la notación decimal.