¿Cómo convertir un número de coma flotante a entero?

47

¿Es esta la forma correcta de hacer conversión de flotante a entero en bash? ¿Hay algún otro método?

flotToint() {
    printf "%.0f\n" "$@"
}
Rahul Patil
fuente
55
¿Qué es "correcto"?
Joseph R.
Solo quiero asegurarme ... ¿es correcto o hay algo mejor que esto?
Rahul Patil
2
%.0fse redondeará hacia arriba o hacia abajo. ¿Es eso lo que quieres? Puedes usar printf "%d\n" "$@" 2>/dev/nullpara cortar la fracción.
ott--

Respuestas:

70

golpetazo

En bash, eso es probablemente tan bueno como se pone. Eso usa un caparazón incorporado. Si necesita el resultado en una variable, puede usar la sustitución de comandos o la bashespecífica (aunque ahora también es compatible con zsh):

printf -v int %.0f "$float"

Podrías hacerlo:

float=1.23
int=${float%.*}

Pero eso eliminaría la parte fraccionaria en lugar de darle el número entero más cercano y eso no funcionaría para valores $floatsimilares 1.2e9o, .12por ejemplo.

También tenga en cuenta las posibles limitaciones debido a la representación interna de flotadores:

$ printf '%.0f\n' 1e50
100000000000000007629769841091887003294964970946560

Sí obtienes un número entero, pero es probable que no puedas usar ese número entero en ninguna parte.

Además, como señaló @BinaryZebra, en varias printfimplementaciones (bash, ksh93, yash, no GNU, zsh, dash), se ve afectado por la configuración regional (el separador decimal que puede ser .o ,).

Por lo tanto, si sus flotantes siempre se expresan con el punto como separador decimal y desea que se trate como tal, printfindependientemente de la configuración regional del usuario que invoca su secuencia de comandos, deberá corregir la configuración regional en C:

LC_ALL=C printf '%.0f' "$float"

Con yash, también puedes hacer:

printf '%.0f' "$(($float))"

(vea abajo).

POSIX

printf "%.0f\n" 1.1

no es POSIX ya %fque no es necesario que sea compatible con POSIX.

POSIXY, puedes hacer:

f2i() {
  awk 'BEGIN{for (i=1; i<ARGC;i++)
   printf "%.0f\n", ARGV[i]}' "$@"
}

Ese no se ve afectado por la configuración regional (la coma no puede ser un separador decimal awkya que ya es un carácter especial en la sintaxis allí ( print 1,2igual que print 1, 2para pasar dos argumentos print)

zsh

En zsh(que admite aritmética de coma flotante (el separador decimal es siempre el punto)), tiene la rint()función matemática para darle el entero más cercano como flotante (como en C) y int()para darle un entero desde un flotante (como en awk). Entonces puedes hacer:

$ zmodload zsh/mathfunc
$ i=$((int(rint(1.234e2))))
$ echo $i
123

O:

$ integer i=$((rint(5.678e2)))
$ echo $i
568

Sin embargo, tenga en cuenta que si bien doubles puede representar números muy grandes, los enteros son mucho más limitados.

$ printf '%.0f\n' 1e123
999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376
$ echo $((int(1e123)))
-9223372036854775808

ksh93

ksh93 fue el primer shell similar a Bourne que admitió la aritmética de coma flotante. ksh93 optimiza la sustitución de comandos al no usar una tubería o bifurcación cuando los comandos son solo comandos incorporados. Entonces

i=$(printf '%.0f' "$f")

no se bifurca O mejor:

i=${ printf '%.0f' "$f"; }

que tampoco se bifurca pero tampoco se toma la molestia de crear un entorno falso de subshell.

También puedes hacer:

i=$((rint(f)))

Pero cuidado con:

$ echo "$((rint(1e18)))"
1000000000000000000
$ echo "$((rint(1e19)))"
1e+19

También puedes hacer:

integer i=$((rint(f)))

Pero como por zsh:

$ integer i=1e18
$ echo "$i"
1000000000000000000
$ integer i=1e19
$ echo "$i"
-9223372036854775808

Tenga en cuenta que la ksh93aritmética de coma flotante respeta la configuración del separador decimal en la configuración regional (aunque de ,lo contrario es un operador matemático ( $((1,2))sería 6/5 en una configuración regional en francés / alemán ... y lo mismo que $((1, 2)), eso es 2 en una configuración regional en inglés) .

yash

yash también admite aritmética de coma flotante, pero no tiene funciones matemáticas como ksh93/ zsh's rint(). Sin embargo, puede convertir un número a entero utilizando el binario o el operador (también funciona zshpero no en ksh93). Sin embargo, tenga en cuenta que trunca la parte decimal, no le da el entero más cercano:

$ echo "$((0.237e2 | 0))"
23
$ echo "$((1e19))"
-9223372036854775808

yash respeta el separador decimal de la configuración regional en la salida, pero no para las constantes literales de coma flotante en sus expresiones aritméticas, lo que puede causar sorpresas:

$ LC_ALL=fr_FR.UTF-8 ./yash -c 'a=$((1e-2)); echo $(($a + 1))'
./yash: arithmetic: `,' is not a valid number or operator

Es bueno de alguna manera, ya que puede usar constantes de punto flotante en sus scripts que usan el punto y no tiene que preocuparse de que deje de funcionar en otras configuraciones regionales, pero aún así pueda manejar los números expresados ​​por el usuario durante tanto tiempo. como recuerdas hacer:

var=$((10.3)) # and not var=10.3
... "$((a + 0.1))" # and not "$(($a + 0.1))".

printf '%.0f\n' "$((10.3))" # and not printf '%.0f\n' 10.3
Stéphane Chazelas
fuente
int=${float%.*}funcionó muy bien en mi script bash (versión 3.2.57 (1) -release) en una Mac (versión 10.13.4 (17E199)).
TelamonAegisthus
Su primera formulación falla en GNU bash 4.4.19, por ejemplo: `` `$ echo $ maximum 32.800 $ printf -v int% .0f" $ maximum "bash: printf: 32.800: número inválido` ``
Luís de Sousa
@ LuísdeSousa, su configuración regional probablemente tiene ,la raíz decimal. Ver la sección sobreLC_ALL=C
Stéphane Chazelas
Tenga en cuenta que esta respuesta responde a una pregunta diferente: cómo eliminar dígitos después del punto, no lo que se le preguntó: cuál es la forma correcta de hacer flotante a entero .
Isaac
¿Debería este método aplicarse a flotantes de cualquier número de bits? ¿Debería ser lo mismo para una flotante de 23 bits o una mantisa de 128 bits?
Isaac
21

bc - Un lenguaje de calculadora de precisión arbitraria

int (float) debería verse así:

$ echo "$float/1" | bc 
1234

Para redondear mejor use esto:

$ echo "($float+0.5)/1" | bc 

Ejemplo:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2
mundo inocente
fuente
44
Pero float=-2; echo "($float+0.5)/1" | bcda -1.
Stéphane Chazelas
2
Eso se llama redondo hacia + inf. Esa es una de las cuatro formas posibles de redondear .
5

La respuesta anterior presentada fue casi correcta: "Podría hacer:

float=1.23
int=${float%.*}

Pero eso eliminaría la parte fraccionaria en lugar de darle el número entero más cercano y eso no funcionaría para valores de $ float como 1.2e9 o .12, por ejemplo ... "

Solo úsalo ${float%%.*}.

echo ${float%%.*}
1
Jay Mayers
fuente
3

Un hacky muy simple es

function float_to_int() { 
  echo $1 | cut -d. -f1    # or use -d, if decimals separator is ,
}

Salida de muestra

$ float_to_int 32.333
32
$ float_to_int 32
32
GMaster
fuente
Sería posible usarlo sed, pero la cutsolución es más simple. Prefiero sedo cutya que están en mi humilde opinión más disponibles en objetivos incrustados que awk(que todavía es bastante común a través de busyboxque bc).
Pevik
0

Relacionado:

  1. cómo hacer flotar a entero en awk ,
  2. ¿Cómo redondear números de coma flotante en shell?

Complementando la respuesta de @ Stéphane Chazelas awk :

float_to_integer_rounding_nearst() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%.0f", ARGV[i]}' "$@";
}

float_to_integer_rounding_floor() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%d", int( ARGV[i] )}' "$@";
}
usuario
fuente
en mis awks, el último se redondea hacia cero, no a infinito menos (como se puede entender que significa "piso"). Además, la primera ronda mitades a números pares. No estoy seguro de si eso es diferente de lo que hace Bash printf, o si hay alguna otra razón para preferir awk sobre el incorporado printf.
ilkkachu