¿Por qué la división en aritmética bash calcula la mayoría de los porcentajes como 0?

15

Intento de aritmética bash para un script, pero $eno se actualiza hasta el final. La salida habla por sí misma.

max=5
for e in $(seq 1 1 $max); do 
    percent=$(( $e/$max*100 ))
    echo "echo $e / $max : = $percent"
done

Tl; DR: muestra 1..5 como porcentaje.

Salida:

echo 1 / 5 : = 0
echo 2 / 5 : = 0
echo 3 / 5 : = 0
echo 4 / 5 : = 0
echo 5 / 5 : = 100

¿Por qué es esto?

Cybex
fuente
1
Relacionado: Bash solo da un número entero como salida independientemente de la entrada al realizar los cálculos (aunque, a diferencia del problema descrito aquí, eso no se puede resolver bien reordenando las operaciones).
Eliah Kagan

Respuestas:

24

bashno puede manejar aritmética no entera. Le dará el resultado correcto siempre que todas las expresiones sean enteras. Por lo tanto, debe evitar obtener un valor no entero en algún lugar de su cálculo.

En su caso, cuando está evaluando 1 / 5, 2 / 5etc., crea los valores enteros de cero en bash correspondientes a algunos valores no enteros y los resultados salen a cero en consecuencia. La precedencia de la división y la multiplicación son los mismos operadores precedentes y siempre se ejecutan de izquierda a derecha cuando se colocan en la expresión.

Una solución será hacer primero la multiplicación y luego la división para que bash nunca tenga que manejar valores no enteros. La expresión corregida será,

$ max=5; for e in $(seq 1 1 $max); do percent=$(( $e*100/$max )); echo "echo $e / $max : = $percent"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100
souravc
fuente
1
La subexpresión 1/5no crea un valor no entero . Crea el valor entero 0, ya que el resultado de la división entera de 1 por 5 es 0. Otras operaciones utilizan con éxito ese valor de 0. Esto no es lo que pretendía el OP, pero ninguna operación crea un valor no entero y ninguna operación falla.
Eliah Kagan el
@EliahKagan gracias por señalar la falla. Editado
souravc
16

A Bash no le va muy bien en este tipo de aritmética ... Aquí está su problema:

$ echo $((1/5))
0
$ echo $((2/5))
0
$ echo $((4/5))
0
$ echo $((4/5))
0

Si necesita manejar valores no enteros, puede usar bc

$ max=5; for e in $(seq 1 1 "$max"); do percent=$(bc <<< "scale=1 ; $e/$max*100") ; echo "echo $e / $max : = ${percent%.*}"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100

(gracias a @Arronical por señalar cómo formatear la salida como enteros)

Zanna
fuente
Definitivamente voy a echar un vistazo a esto, ¡gracias una vez más!
Cybex
1
Puede recortar el .0de la salida cambiando $percent su eco a ${percent%.*}:)
Arronical
11

A diferencia de bash, awk ofrece aritmética de coma flotante completa. Por ejemplo:

$ awk -v max=5 'BEGIN{for (e=1;e<=max;e++) print "echo " e " / " max " : = " 100*e/max}' 
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100
John1024
fuente
5

Tratar

percent=$(( $e*100/$max ))

:)

Ver sección EVALUACIÓN ARITMÉTICA de:

man bash

Solo admite enteros.

Alfredo
fuente