Как преобразовать число с плавающей точкой в ​​целое число?

47

Правильный ли это способ преобразования чисел с плавающей точкой в ​​bash? Есть ли другой метод?

flotToint() {
    printf "%.0f\n" "$@"
}
Рахул Патил
источник
5
Что правильно"?
Джозеф Р.
Я просто хочу убедиться ... это правильно или есть что-то лучше?
Рахул Патил
2
%.0fбудет округлять вверх или вниз. Это то, что вы хотите? Вы можете использовать printf "%d\n" "$@" 2>/dev/nullдля измельчения дроби.
ot--

Ответы:

70

удар

В bash, это, вероятно, так же хорошо, как это получается. Это использует встроенную оболочку. Если вам нужен результат в переменной, вы можете использовать подстановку команд или bashконкретный (хотя теперь также поддерживается zsh):

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

Вы могли бы сделать:

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

Но это уберет дробную часть вместо того, чтобы дать вам ближайшее целое число, и это не сработает для значений $floatlike 1.2e9или, .12например.

Также обратите внимание на возможные ограничения из-за внутреннего представления чисел:

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

Вы получаете целое число, но есть вероятность, что вы нигде не сможете его использовать.

Кроме того, как отмечает @BinaryZebra, в нескольких printfреализациях (bash, ksh93, yash, а не GNU, zsh, dash) на него влияет локаль (десятичный разделитель, который может быть .или ,).

Итак, если ваши значения с плавающей точкой всегда выражаются точкой в ​​качестве десятичного разделителя, и вы хотите, чтобы он обрабатывался как таковой printfнезависимо от языкового стандарта пользователя, вызывающего ваш скрипт, вам нужно будет установить языковой стандарт на C:

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

С помощью yashвы также можете сделать:

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

(Смотри ниже).

POSIX

printf "%.0f\n" 1.1

не POSIX, так как %fне требуется для поддержки POSIX.

POSIXly, вы можете сделать:

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

На него не влияет языковой стандарт (запятая не может быть десятичным разделителем, awkпоскольку там уже есть специальный символ в синтаксисе ( print 1,2аналогично print 1, 2передаче двух аргументов print)

ЗШ

В zsh(которая поддерживает арифметику с плавающей запятой (десятичный разделитель всегда является точкой)), у вас есть rint()математическая функция, которая дает вам ближайшее целое число в виде числа с плавающей запятой (как в C) и int()дает вам целое число из числа с плавающей запятой (как в awk). Так что вы можете сделать:

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

Или же:

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

Однако обратите внимание, что хотя doubles может представлять очень большие числа, целые числа гораздо более ограничены.

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

ksh93

ksh93 была первой подобной Борну оболочкой, которая поддерживает арифметику с плавающей запятой. ksh93 оптимизирует подстановку команд, не используя канал или разветвление, когда команды являются только встроенными командами. Так

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

не развивается Или даже лучше:

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

который также не работает, но также не создает проблем при создании поддельной оболочки.

Вы также можете сделать:

i=$((rint(f)))

Но остерегайтесь:

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

Вы также можете сделать:

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

Но, как для zsh:

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

Помните, что ksh93арифметика с плавающей запятой учитывает настройку десятичного разделителя в локали (хотя ,в противном случае это математический оператор ( $((1,2))будет 6/5 во французской / немецкой ... локали, и то же самое $((1, 2)), что 2 в английской локали) ,

йаш

йаш также поддерживает арифметику с плавающей , но не имеет математические функции , такие как ksh93/ zsh«s rint(). Вы можете преобразовать число в целое число, используя, например, двоичный файл или оператор (также работает в, zshно не в ksh93). Обратите внимание, что он усекает десятичную часть, но не дает ближайшего целого числа:

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

yash учитывает десятичный разделитель локали при выводе, но не для литеральных констант с плавающей запятой в их арифметических выражениях, что может вызвать неожиданность:

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

Это хорошо в том смысле, что вы можете использовать константы с плавающей запятой в ваших скриптах, которые используют точку, и вам не нужно беспокоиться о том, что она перестанет работать в других локалях, но при этом сможете обрабатывать числа, выраженные пользователем до тех пор, пока как вы не забыли сделать:

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
Стефан Шазелас
источник
int=${float%.*}очень хорошо работал в моем скрипте bash (версия 3.2.57 (1) -релиз) на Mac (версия 10.13.4 (17E199)).
TelamonAegisthus
Ваша первая формулировка не удалась в GNU bash 4.4.19, например: `` `$ echo $ максимум 32.800 $ printf -v int% .0f" $ максимум "bash: printf: 32.800: недопустимое число` ``
Луис де Соуза
@ LuísdeSousa, вероятно, в твоей локали ,десятичное число. Смотрите раздел оLC_ALL=C
Стефан
Обратите внимание, что этот ответ отвечает на другой вопрос: как убрать цифры после точки, а не то, что было задано: как правильно делать с плавающей точкой целое число .
Исаак
Должен ли этот метод применяться к плавающим числам любого числа? Должно ли это быть одинаковым для числа с 23 или 128 битами мантиссы?
Исаак
21

bc - язык калькулятора произвольной точности

int (float) должен выглядеть так:

$ echo "$float/1" | bc 
1234

Для округления лучше использовать это:

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

Пример:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2
невинно мир
источник
4
Но float=-2; echo "($float+0.5)/1" | bcдает -1.
Стефан Шазелас
2
Это называется раунд в сторону + инф. Это один из четырех возможных способов округления .
5

Предыдущий ответ был почти верным: «Вы можете сделать:

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

Но это уберет дробную часть вместо того, чтобы дать вам ближайшее целое число, и это не сработает для значений $ float, таких как, например, 1.2e9 или .12 .... "

Просто используйте ${float%%.*}.

echo ${float%%.*}
1
Джей Майерс
источник
3

Очень простой взломанный

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

Образец вывода

$ float_to_int 32.333
32
$ float_to_int 32
32
GMaster
источник
Можно было бы использовать sed, но cutрешение проще. Я предпочитаю sedили cutкак они ИМХО более доступны на встроенных целях , чем awk(что по- прежнему довольно часто через busyboxчем bc).
Певик
0

Связанный:

  1. как заставить число с плавающей точкой в ​​awk ,
  2. Как округлить числа с плавающей точкой в ​​оболочке?

Дополняя ответ Стефана Шазеля 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] )}' "$@";
}
пользователь
источник
по моим пробуждениям, последний округляется к нулю, а не к минус бесконечности (как можно понимать как «пол»). Также первые раунды делятся пополам до четных чисел. Я не уверен, отличается ли это от того, что printfделает Bash , или есть какая-то другая причина, чтобы предпочесть awk встроенному printf.
ilkkachu