Esta respuesta a la primera pregunta vinculada tiene la línea casi desechable al final:
Consulte también %g
para redondear a un número específico de dígitos significativos.
Entonces puedes simplemente escribir
printf "%.2g" "$n"
(pero consulte la siguiente sección sobre separador decimal y configuración regional, y tenga en cuenta que no es printf
necesario que Bash no sea compatible %f
y %g
).
Ejemplos:
$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077
Por supuesto, ahora tiene una representación de exponente de mantisa en lugar de un decimal puro, por lo que querrá convertir de nuevo:
$ printf "%0.f\n" 7.7e+06
7700000
$ printf "%0.7f\n" 7.7e-06
0.0000077
Poniendo todo esto junto, y envolviéndolo en una función:
# Function round(precision, number)
round() {
n=$(printf "%.${1}g" "$2")
if [ "$n" != "${n#*e}" ]
then
f="${n##*e-}"
test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
printf "%0.${f}f" "$n"
else
printf "%s" "$n"
fi
}
(Nota: esta función está escrita en un shell portátil (POSIX), pero se supone que printf
maneja las conversiones de punto flotante. Bash tiene una función incorporada printf
que sí, así que estás bien aquí, y la implementación de GNU también funciona, por lo que la mayoría de GNU / Los sistemas Linux pueden usar Dash de forma segura).
Casos de prueba
radix=$(printf %.1f 0)
for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e' -e "y/_._/$radix/")
do
echo $i "->" $(round 2 $i)
done
Resultados de la prueba
.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000
Una nota sobre separador decimal y locale
Todo el trabajo anterior supone que el carácter radix (también conocido como separador decimal) es .
, como en la mayoría de las configuraciones regionales inglesas. En su ,
lugar, se utilizan otras configuraciones regionales , y algunas shells tienen una printf
configuración regional que respeta la configuración regional. En estos shells, es posible que deba configurar LC_NUMERIC=C
para forzar el uso de .
como carácter de raíz o escribir /usr/bin/printf
para evitar el uso de la versión incorporada. Esto último se complica por el hecho de que (al menos algunas versiones) parecen analizar siempre los argumentos usando .
, pero imprimen usando la configuración regional actual.
%f
/%g
, pero ese es elprintf
argumento, y uno no necesita un POSIXprintf
para tener un shell POSIX. Creo que deberías haber comentado en lugar de editar allí.printf %g
no se puede usar en un script POSIX. Es cierto que se trata de laprintf
utilidad, pero esa utilidad está integrada en la mayoría de los depósitos. El OP etiquetado como bash, por lo que usar bash shebang es una manera fácil de obtener una printf que admita% g. De lo contrario, necesitaría agregar un asumiendo que su printf (o el printf incorporado de sush
ifprintf
está incorporado allí) admite el no estándar (pero bastante común)%g
...dash
's tiene un incorporadoprintf
(que soporta%g
). En los sistemas GNU,mksh
es probable que sea el único shell en estos días que no tendrá una función integradaprintf
.bash
) y relegar parte de esto a las notas. ¿Parece correcto ahora?printf "%.3g\n" 0.400
da 0.4 no 0.400TL; DR
Simplemente copie y use la función
sigf
en la secciónA reasonably good "significant numbers" function:
. Está escrito (como todo el código en esta respuesta) para trabajar con el guión .Le dará la
printf
aproximación a la parte entera de N con$sig
dígitos.Sobre el separador decimal.
El primer problema a resolver con printf es el efecto y el uso de la "marca decimal", que en EE. UU. Es un punto, y en DE es una coma (por ejemplo). Es un problema porque lo que funciona para alguna configuración regional (o shell) fallará con otra configuración regional. Ejemplo:
Una solución común (e incorrecta) es establecer
LC_ALL=C
el comando printf. Pero eso establece la marca decimal en un punto decimal fijo. Para entornos locales donde una coma (u otra) es el carácter utilizado comúnmente que es un problema.La solución es descubrir dentro del script para el shell que lo ejecuta cuál es el separador decimal de la configuración regional. Eso es bastante simple:
Eliminar ceros:
Ese valor se usa para cambiar el archivo con la lista de pruebas:
Eso hace que las ejecuciones en cualquier shell o configuración regional sean automáticamente válidas.
Algunos conceptos básicos
Debería ser intuitivo cortar el número que se formateará con el formato
%.*e
o incluso con%.*g
printf. La principal diferencia entre usar%.*e
o%.*g
es cómo cuentan los dígitos. Uno usa el conteo completo, el otro necesita el conteo menos 1:Eso funcionó bien durante 4 dígitos significativos.
Después de que el número de dígitos se ha cortado del número, necesitamos un paso adicional para formatear números con exponentes diferentes a 0 (como se indicó anteriormente).
Esto funciona correctamente El recuento de la parte entera (a la izquierda de la marca decimal) es solo el valor del exponente ($ exp). El recuento de decimales necesarios es la cantidad de dígitos significativos ($ sig) menos la cantidad de dígitos ya utilizados en la parte izquierda del separador decimal:
Como la parte integral del
f
formato no tiene límite, de hecho no hay necesidad de declararlo explícitamente y este código (más simple) funciona:Primer intento.
Una primera función que podría hacer esto de una manera más automatizada:
Este primer intento funciona con muchos números, pero fallará con números para los cuales la cantidad de dígitos disponibles es menor que el recuento significativo solicitado y el exponente es menor que -4:
Agregará muchos ceros que no son necesarios.
Segundo juicio
Para resolver eso, necesitamos limpiar N del exponente y los ceros finales. Entonces podemos obtener la longitud efectiva de dígitos disponibles y trabajar con eso:
Sin embargo, eso está usando matemática de coma flotante, y "nada es simple en coma flotante": ¿Por qué no se suman mis números?
Pero nada en "coma flotante" es simple.
Sin embargo:
¿Por qué?:
Y, además, el comando
printf
está integrado por muchos proyectiles.Qué
printf
impresiones pueden cambiar con el shell:Una función razonablemente buena de "números significativos":
Y los resultados son:
fuente
Si ya tiene el número como una cadena, es decir, como "3456" o "0.003756", entonces podría hacerlo solo utilizando la manipulación de la cadena. Lo siguiente está fuera de mi cabeza, y no se probó a fondo, y usa sed, pero considere:
Donde básicamente te quitas y guardas cualquier cosa "-0,000" al principio, luego usas una operación de subcadena simple en el resto. Una advertencia sobre lo anterior es que no se eliminan múltiples ceros iniciales. Lo dejaré como ejercicio.
fuente