Solo se requiere un script de shell compatible con POSIX para admitir integer aritmética utilizando el lenguaje shell ("solo se requiere aritmética de enteros largos con signo"), por lo que un shell puro la solución debe emular aritmética de coma flotante:
item=30
total=70
percent=$(( 100 * item / total + (1000 * item / total % 10 >= 5 ? 1 : 0) ))
100 * item / total
produce el truncado resultado del entero división como porcentaje.1000 * item / total % 10 >= 5 ? 1 : 0
calcula el 1er lugar decimal, y si es igual o mayor a 5, suma 1 al resultado entero para redondearlo.- Tenga en cuenta que no necesita prefijar referencias variables con
$
dentro de una expansión aritmética$((...))
.
Si - en contradicción con la premisa de la pregunta - uso de utilidades externas es aceptable:
awk
ofrece un simple solución, que, sin embargo, viene con la advertencia que utiliza verdadero binario de doble precisión valores de coma flotante y, por lo tanto, pueden producir resultados inesperados en decimal representación - por ejemplo, intenteprintf '%.0f\n' 28.5
, que produce28
en lugar del esperado29
):
awk -v item=30 -v total=70 'BEGIN { printf "%.0f\n", 100 * item / total }'
- Observe cómo
-v
se utiliza para definir variables para elawk
script, que permite una separación clara entre las comillas simples y por lo tanto literalawk
script y cualquier valor que se le haya pasado desde el shell .
- Por el contrario, aunque
bc
es una utilidad POSIX (y, por lo tanto, se puede esperar que esté presente en la mayoría de las plataformas similares a Unix) y realiza precisión arbitraria aritmética, invariablemente trunca los resultados, de modo que redondeando debe ser realizado por otro utilidad;printf
, sin embargo, aunque en principio es una utilidad POSIX, no se requiere para admitir especificadores de formato de coma flotante (como los que se usan dentro deawk
anterior), por lo que lo siguiente puede o no funcionar (y no vale la pena, dado elawk
más simple solución, y dado que los problemas de precisión debido a la aritmética de punto flotante están de vuelta en la imagen):
# !! This MAY work on your platform, but is NOT POSIX-compliant:
# `-l` tells `bc` to set the precision to 20 decimal places, `printf '%.0f\n'`
# then performs the rounding to an integer.
item=20 total=70
printf '%.0f\n' "$(bc -l <<EOF
100 * $item / $total
EOF
)"
Usa AWK (sin bash-ismos):
item=30
total=70
percent=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }")
echo $percent
43
Tomando 2 * el cálculo porcentual original y obteniendo el módulo 2 proporciona el incremento para el redondeo.
item=30
total=70
percent=$((200*$item/$total % 2 + 100*$item/$total))
echo $percent
43
(probado con bash, ash, dash y ksh)
Esta es una implementación más rápida que disparar un coproceso AWK:
$ pa() { for i in `seq 0 1000`; do pc=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }"); done; }
$ time pa
real 0m24.686s
user 0m0.376s
sys 0m22.828s
$ pb() { for i in `seq 0 1000`; do pc=$((200*$item/$total % 2 + 100*$item/$total)); done; }
$ time pb
real 0m0.035s
user 0m0.000s
sys 0m0.012s