Comment convertir un nombre à virgule flottante en entier?

47

Est-ce la bonne façon de faire une conversion de flottant en entier dans bash? Y a-t-il une autre méthode?

flotToint() {
    printf "%.0f\n" "$@"
}
Rahul Patil
la source
5
Qu'est-ce qui est "juste"?
Joseph R.
Je veux juste m'assurer… est-ce exact ou y a-t-il mieux que cela?
Rahul Patil
2
%.0farrondira de haut en bas. Est-ce que c'est ce que tu veux? Vous pouvez utiliser printf "%d\n" "$@" 2>/dev/nullpour couper la fraction.
ott--

Réponses:

70

frapper

Dans bash, c'est probablement aussi bon que cela. Cela utilise un shell intégré. Si vous avez besoin du résultat dans une variable, vous pouvez utiliser la substitution de commande, ou la commande bashspécifique (bien que désormais également prise en charge par zsh):

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

Vous pourriez faire:

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

Mais cela supprimerait la partie décimale au lieu de vous donner l'entier le plus proche et cela ne fonctionnerait pas pour des valeurs de $floattype similaire 1.2e9ou .12par exemple.

Notez également les limitations possibles dues à la représentation interne des floats:

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

Vous obtenez un entier, mais il est probable que vous ne pourrez utiliser cet entier nulle part.

De plus, comme l'a noté @BinaryZebra, dans plusieurs printfimplémentations (bash, ksh93, yash, et non GNU, zsh, dash), il est affecté par les paramètres régionaux (le séparateur décimal qui peut être .ou ,).

Ainsi, si vos flottants sont toujours exprimés avec le point comme séparateur décimal et que vous souhaitez le traiter comme tel printfquel que soit le paramètre régional de l'utilisateur qui appelle votre script, vous devez le définir en C:

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

Avec yash, vous pouvez aussi faire:

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

(voir ci-dessous).

POSIX

printf "%.0f\n" 1.1

ne POSIX %fn'a pas à être pris en charge par POSIX.

POSIXly, vous pouvez faire:

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

Celui-ci n'est pas affecté par les paramètres régionaux (la virgule ne peut pas être un séparateur décimal, awkcar il s'agit déjà d'un caractère spécial dans la syntaxe ( print 1,2identique print 1, 2à celle utilisée pour passer deux arguments print).

zsh

Dans zsh(qui prend en charge l’arithmétique en virgule flottante (le séparateur décimal est toujours la période)), vous avez la rint()fonction mathématique qui vous donne l’entier le plus proche sous forme de float (comme dans C) et int()un entier à partir de float (comme dans awk). Alors tu peux faire:

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

Ou:

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

Cependant, notez que doubles peut représenter de très grands nombres, les nombres entiers sont beaucoup plus limités.

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

ksh93

ksh93 était le premier shell de type Bourne à supporter l'arithmétique en virgule flottante. ksh93 optimise la substitution de commande en n'utilisant pas de canal ou en forçant lorsque les commandes ne sont que des commandes intégrées. Alors

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

ne fourche pas. Ou même mieux:

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

ce qui ne va pas non plus, mais ne va pas non plus tout le problème de la création d’un faux environnement de sous-shell.

Vous pouvez aussi faire:

i=$((rint(f)))

Mais méfiez-vous de:

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

Vous pouvez aussi faire:

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

Mais comme pour zsh:

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

Attention, l' ksh93arithmétique en virgule flottante respecte le paramètre de séparateur décimal défini dans l'environnement local (même s'il ,s'agit par ailleurs d'un opérateur mathématique ( $((1,2))serait 6/5 dans un environnement français / allemand ... et identique $((1, 2)), 2 dans un environnement anglais) .

yash

Yash prend également en charge l' arithmétique en virgule flottante , mais ne dispose pas de fonctions mathématiques comme ksh93/ zshs » rint(). Vous pouvez toutefois convertir un nombre en entier en utilisant le binaire ou l' opérateur par exemple (fonctionne également dans zshmais pas dans ksh93). Notez cependant que cela tronque la partie décimale, il ne vous donne pas l'entier le plus proche:

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

yash respecte le séparateur décimal des paramètres régionaux en sortie, mais pas les constantes littérales à virgule flottante dans ses expressions arithmétiques, ce qui peut entraîner des surprises:

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

En un sens, vous pouvez utiliser des constantes à virgule flottante dans vos scripts qui utilisent la période et ne pas avoir à craindre que celle-ci ne fonctionne plus dans d'autres paramètres régionaux, tout en conservant la capacité de traiter les nombres tels qu'exprimés par l'utilisateur aussi longtemps. comme tu te souviens de le faire:

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
la source
int=${float%.*}a très bien fonctionné dans mon script bash (version 3.2.57 (1) -release) sur Mac (version 10.13.4 (17E199)).
TelamonAegisthus
Votre première formulation échoue dans GNU bash 4.4.19, par exemple: `` `$ echo $ maximum 32.800 $ printf -v int% .0f" $ maximum "bash: printf: 32.800: nombre invalide` ``
Luís de Sousa
@ LuísdeSousa, vos paramètres régionaux ont probablement ,pour base décimale. Voir la section à propos deLC_ALL=C
Stéphane Chazelas Le
Notez que cette réponse répond à une question différente: comment supprimer les chiffres après le point et non ce qui a été demandé: quelle est la bonne manière de faire de float to integer .
Isaac
Cette méthode doit-elle s'appliquer aux flottants d'un nombre quelconque de bits? Devrait-il en être de même pour un float de mantisse 23 bits ou 128 bits?
Isaac
21

bc - Un langage de calculateur de précision arbitraire

int (float) devrait ressembler à:

$ echo "$float/1" | bc 
1234

Pour arrondir mieux utiliser ceci:

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

Exemple:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2
monde innocent
la source
4
Mais float=-2; echo "($float+0.5)/1" | bcdonne -1.
Stéphane Chazelas
2
Cela s'appelle round vers + inf. C’est l’une des quatre manières possibles d’arriver .
5

La réponse précédente était presque correcte: "Vous pourriez faire:

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

Mais cela supprimerait la partie décimale au lieu de vous donner l’entier le plus proche et cela ne fonctionnerait pas pour les valeurs de $ float telles que 1.2e9 ou .12 par exemple ... "

Il suffit d'utiliser ${float%%.*}.

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

Un hacky très simple est

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

Échantillon de sortie

$ float_to_int 32.333
32
$ float_to_int 32
32
GMaster
la source
Il serait possible de l'utiliser sed, mais la cutsolution est plus simple. Je préfère sedou cutcomme ils sont à mon humble avis plus disponible sur les cibles intégrées que awk(ce qui est encore assez commun via busyboxque bc).
Pevik
0

Apparenté, relié, connexe:

  1. comment faire flotter à un entier dans awk ,
  2. Comment arrondir des nombres à virgule flottante dans un shell?

Complétant @ Stéphane Chazelas awk réponse:

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] )}' "$@";
}
utilisateur
la source
sur mon arrière-plan, le dernier arrondit vers zéro, pas à moins l'infini (comme peut signifier "plancher"). En outre, les premiers tours sont réduits de moitié en nombres pairs. Je ne suis pas sûr que ce soit différent de ce que fait Bash printfou s'il y a une autre raison de préférer awk à la fonction intégrée printf.
ilkkachu