¿Cómo uso la división de punto flotante en bash?

242

Estoy tratando de dividir dos anchos de imagen en un script Bash, pero bash me da 0como resultado:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Estudié la guía Bash y sé que debería usar bc, en todos los ejemplos que usan en Internet bc. En echoTraté de poner la misma cosa en mi SCALE, pero no funcionó.

Aquí está el ejemplo que encontré en los tutoriales:

echo "scale=2; ${userinput}" | bc 

¿Cómo puedo hacer que Bash me dé un flotador 0.5?

Medya Gh
fuente
99
Un comentario para todos los que intenten hacer aritmética de coma flotante en su script, pregúntense: ¿realmente necesito aritmética de coma flotante? a veces realmente puedes llevarte bien sin él. Véase, por ejemplo, la última parte de BashFAQ / 022 .
gniourf_gniourf

Respuestas:

242

No puedes bash solo hace enteros; se debe delegar a una herramienta como bc.

Ignacio Vazquez-Abrams
fuente
8
¿Cómo puedo delegar una herramienta como bc para poner la respuesta en la variable RESULT?
Medya Gh
2
entonces te refieres a VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh
54
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)o VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")sin $ (()) (paréntesis dobles); que es expandido por el bash antes de ejecutar el comando
Nahuel Fouilleul
44
Es cierto, pero es más probable que awk ya esté instalado en el sistema.
CMCDragonkai
99
@NahuelFouilleul Tienes la mejor respuesta aquí. Realmente debería ser su propia respuesta, y aceptado como la respuesta. Esta línea en particular fue increíblemente útil: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David
197

Puedes hacerlo:

bc <<< 'scale=2; 100/3'
33.33

ACTUALIZACIÓN 20130926 : puede usar:

bc -l <<< '100/3' # saves a few hits
aayoubi
fuente
8
... y agrega muchos dígitos. Al menos en mi máquina, esto produce 33.33333333333333333333 mientras que el primero da 33.33.
Andreas Spindler
12
@AndreasSpindler Tipo de una publicación anterior, pero en caso de que a alguien le gustaría saber, esto se puede cambiar aplicando el comando de escala, por ejemplo. bc -l <<< 'scale=2; 100/3'
Martie
Solo tenga cuidado si espera obtener un número entero para usarlo más tarde en bash usando scale = 0. A partir de v1.06.95, bc, por alguna razón, ignora la variable de escala cuando los números de entrada tienen una parte decimal. Tal vez esto está en los documentos, pero no pude encontrarlo. Prueba: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell
1
@GregBell La página del manual dice Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.Y hay una nota adicional para el /operador:The scale of the result is the value of the variable scale.
psmith
2
Gracias @psmith. Curiosamente, para / dice "la escala del resultado es el valor de la escala variable" pero no así para la multiplicación. Mis mejores ejemplos: la bc <<< 'scale=1; 1*3.00001' escala es realmente 5 por alguna razón, la bc <<< 'scale=1; 1/3.000001' escala es 1. Curiosamente, dividir por 1 lo pone en orden: la bc <<< 'scale=1; 1*3.00001/1'escala es 1
Greg Bell
136

intento

Como han señalado otros, bashno admite la aritmética de coma flotante, aunque podría simularla con algún truco decimal fijo, por ejemplo, con dos decimales:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Salida:

.33

Vea la respuesta de Nilfred para un enfoque similar pero más conciso.

Alternativas

Además de lo mencionado bcy las awkalternativas, también están las siguientes:

clisp

clisp -x '(/ 1.0 3)'

con salida limpiada:

clisp --quiet -x '(/ 1.0 3)'

o a través de stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

corriente continua

echo 2k 1 3 /p | dc

calculadora cli genio

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

gnuplot

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

O:

jq -n 1/3

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

o a través de stdin:

echo 'print(1/3)' | lua

maxima

echo '1/3,numer;' | maxima

con salida limpiada:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

nodo

echo 1/3 | node -p

octava

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

con salida limpiada:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

rubí

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Con salida limpiada:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

echo 'print $(( 1/3. ))' | zsh

unidades

units 1/3

Con salida compacta:

units --co 1/3

Otras fuentes

Stéphane Chazelas respondió una pregunta similar en Unix.SX.

Thor
fuente
99
Gran respuesta. Sé que fue publicado unos años después de la pregunta, pero merece más ser la respuesta aceptada.
Brian Cline
2
Si tiene zsh disponible, entonces vale la pena considerar escribir su script en zsh en lugar de Bash
Andrea Corbellini
Thumb up for gnuplot :)
andywiecko
Buena lista Especialmente echo 1/3 | node -pes corto.
Johnny Wong
39

Mejorando un poco la respuesta de marvin:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc no siempre viene como paquete instalado.

adrianlzt
fuente
44
El script awk necesita un exitpara evitar que lea desde su flujo de entrada. También sugiero usar las -vbanderas de awk para prevenir el síndrome del palillo inclinado. Entonces:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley
2
Una forma más awkish de hacer esto sería leer los argumentos de la corriente de entrada: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster
1
bces parte de POSIX, generalmente está preinstalado.
fuz
esto funcionó para mí usando git bash en Windows 7 ... gracias :)
The Beast
31

Puede usar bc por la -lopción (la letra L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
usuario1314742
fuente
44
Si no incluyo el -len mi sistema, bc no hace matemática de punto flotante.
starbeamrainbowlabs
28

Como alternativa a bc, puede usar awk dentro de su script.

Por ejemplo:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

En lo anterior, "% .2f" le dice a la función printf que devuelva un número de coma flotante con dos dígitos después del decimal. Usé echo para canalizar las variables como campos ya que awk funciona correctamente en ellas. "$ 1" y "$ 2" se refieren a la entrada del primer y segundo campo en awk.

Y puede almacenar el resultado como alguna otra variable usando:

RESULT = `echo ...`
marvin
fuente
¡Excelente! Gracias. Esto es útil para entornos integrados donde bc no está presente. Me ahorraste tiempo de compilación cruzada.
enthusiasticgeek
19

Es el momento perfecto para probar zsh, un superconjunto (casi) bash, con muchas características adicionales que incluyen matemáticas de coma flotante. Así es como sería su ejemplo en zsh:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Esta publicación puede ayudarte: bash - ¿Vale la pena cambiar a zsh para uso casual?

Penghe Geng
fuente
3
Soy un gran fanático de zsh y lo he estado usando durante los últimos 4 años, pero el uso interactivo es un buen énfasis aquí. Un script que requiere zsh generalmente no será muy portátil en un conjunto diverso de máquinas, ya que generalmente no es estándar, lamentablemente (para ser justos, tal vez esté bien; OP no dijo exactamente cómo se usará).
Brian Cline
17

Bueno, antes de flotar era un momento en el que se usaba la lógica de decimales fijos:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

La última línea es un bashim, si no usa bash, intente este código en su lugar:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

La razón detrás del código es: multiplique por 100 antes de dividir para obtener 2 decimales.

Nilfred
fuente
5

Si encontró la variante de su preferencia, también puede incluirla en una función.

Aquí estoy envolviendo algo de bashismo en una función div:

Un trazador de líneas:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

O multilínea:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Ahora tienes la función

div <dividend> <divisor> [<precision=2>]

y úsalo como

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

ACTUALIZACIÓN Agregué un pequeño script que le proporciona las operaciones básicas con números de punto flotante para bash:

Uso:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

Y aquí el guión:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}
bebbo
fuente
1
local _d=${3:-2}es más simple
KamilCuk
4

No es realmente punto flotante, pero si quieres algo que establezca más de un resultado en una invocación de bc ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

calcula el área y el diámetro de un círculo cuyo radio se da en $ 1

usuario3210339
fuente
4

Hay escenarios en los que no puede usar bc porque simplemente podría no estar presente, como en algunas versiones reducidas de busybox o sistemas integrados. En cualquier caso, limitar las dependencias externas siempre es algo bueno, por lo que siempre puede agregar ceros al número que se divide por (numerador), es decir, multiplicar por una potencia de 10 (debe elegir una potencia de 10 de acuerdo con la precisión que necesita), eso hará que la división genere un número entero. Una vez que tenga ese entero, trátelo como una cadena y coloque el punto decimal (moviéndolo de derecha a izquierda) varias veces igual a la potencia de diez por el que multiplicó el numerador. Esta es una manera simple de obtener resultados flotantes utilizando solo números enteros.

Daniel J.
fuente
1
Incluso Busybox tiene Awk. Quizás debería haber una respuesta Awk más prominente aquí.
tripleee
4

Si bien no puede usar la división de punto flotante en Bash, puede usar la división de punto fijo. Todo lo que necesita hacer es multiplicar sus enteros por una potencia de 10 y luego dividir la parte entera y usar una operación de módulo para obtener la parte fraccional. Redondeo según sea necesario.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))
G. Allen Morris III
fuente
4

Sé que es viejo, pero demasiado tentador. entonces, la respuesta es: no puedes ... pero sí puedes. intentemos esto:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

así obtienes 2 dígitos después del punto, truncado (llámalo redondeando a la parte inferior, jaja) en bash puro (no es necesario iniciar otros procesos). por supuesto, si solo necesita un dígito después del punto, multiplique por 10 y realice el módulo 10.

qué hace esto:

  • primero $ ((...)) hace división entera;
  • segundo $ ((...)) hace una división entera en algo 100 veces más grande, esencialmente moviendo sus 2 dígitos a la izquierda del punto, luego (%) obteniendo solo esos 2 dígitos haciendo un módulo.

bonus track : la versión bc x 1000 tardó 1,8 segundos en mi computadora portátil, mientras que la versión pura bash tardó 0,016 segundos.

івась тарасик
fuente
3

Cómo hacer cálculos de coma flotante en bash:

En lugar de usar " here strings " ( <<<) con el bccomando, como lo hace uno de los ejemplos más votados , aquí está mi bcejemplo de coma flotante favorito , directamente desde la EXAMPLESsección de las bcpáginas man (vea man bclas páginas del manual).

Antes de empezar, saber que una ecuación para pi es: pi = 4*atan(1). a()a continuación se muestra la bcfunción matemática para atan().

  1. Así se almacena el resultado de un cálculo de coma flotante en una variable bash, en este caso en una variable llamadapi . Tenga en cuenta que scale=10establece el número de dígitos decimales de precisión en 10 en este caso. Los dígitos decimales después de este lugar se truncan .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Ahora, para tener una sola línea de código que también imprima el valor de esta variable, simplemente agregue el echocomando al final como un comando de seguimiento, de la siguiente manera. Tenga en cuenta el truncamiento en 10 decimales, según lo ordenado:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Finalmente, echemos un poco de redondeo. Aquí usaremos la printffunción para redondear a 4 decimales. Tenga en cuenta que las 3.14159...rondas ahora a 3.1416. Como estamos redondeando, ya no necesitamos usar scale=10para truncar a 10 decimales, por lo que simplemente eliminaremos esa parte. Aquí está la solución final:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Aquí hay otra gran aplicación y demostración de las técnicas anteriores: medir e imprimir el tiempo de ejecución.

Tenga en cuenta que dt_minse redondea de 0.01666666666...a 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Relacionado:

Gabriel Staples
fuente
1
respuesta sólida con ejemplos detallados y casos de uso y ronda usando printf
downvoteit
2

Usa calc. Es el ejemplo más fácil que encontré:

calc 1 + 1

 2

calc 1/10

 0.1
Camila Pereira
fuente
2

Para aquellos que intentan calcular porcentajes con la respuesta aceptada, pero están perdiendo precisión:

Si ejecuta esto:

echo "scale=2; (100/180) * 180" | bc

Solo obtienes 99.00, lo que está perdiendo precisión.

Si lo ejecutas de esta manera:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Ahora lo tienes 99.99.

Porque está escalando solo en el momento de la impresión.

Consulte aquí

Wadih M.
fuente
1

** Matemáticas de punto flotante a prueba de inyecciones en bash / shell **

Nota: El enfoque de esta respuesta es proporcionar ideas para una solución segura para la inyección para realizar las matemáticas en bash (u otros shells). Por supuesto, se puede usar lo mismo, con pequeños ajustes para realizar un procesamiento avanzado de cadenas, etc.

La mayoría de las soluciones que se presentaron, construyen pequeños scriptlet sobre la marcha, utilizando datos externos (variables, archivos, línea de comando, variables de entorno). La entrada externa se puede usar para inyectar código malicioso en el motor, muchos de ellos

A continuación se muestra una comparación sobre el uso de varios idiomas para realizar cálculos matemáticos básicos, donde el resultado es punto flotante. Calcula A + B * 0.1 (como punto flotante).

Todos los intentos de solución evitan crear scriptlets dinámicos, que son extremadamente difíciles de mantener. En su lugar, usan un programa estático y pasan los parámetros a la variable designada. Manejarán con seguridad los parámetros con caracteres especiales, reduciendo la posibilidad de inyección de código. La excepción es 'BC' que no proporciona facilidad de entrada / salida

La excepción es 'bc', que no proporciona ninguna entrada / salida, todos los datos provienen de programas en stdin y toda la salida va a stdout. Todos los cálculos se ejecutan en un entorno limitado, que no permite efectos secundarios (abrir archivos, etc.). En teoría, la inyección es segura por diseño.

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"
guión
fuente
para python3 con argumentos arbitrarios: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness
0

aquí está el comando awk: -F = separador de campo == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
koolwithk
fuente