Как использовать деление с плавающей точкой в ​​Bash?


242

Я пытаюсь разделить две ширины изображения в скрипте Bash, но Bash дает мне 0 в результате:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Я изучил руководство по Bash и знаю, что должен использовать bcво всех примерах в Интернете, которые они используют bc. Вecho Я пытался поставить такую же вещь в моей , SCALEно это не сработало.

Вот пример, который я нашел в уроках:

echo "scale=2; ${userinput}" | bc 

Как я могу получить Bash, чтобы дать мне как поплавок 0.5?


9
Комментарий для всех, кто пытается сделать арифметику с плавающей точкой в ​​вашем скрипте, спросите себя: действительно ли мне нужна арифметика с плавающей точкой? иногда вы можете обойтись без. Смотрите, например, последнюю часть BashFAQ / 022 .
gniourf_gniourf

Ответы:


242

Ты не можешь bash делает только целые числа; Вы должны делегировать такие инструменты, как bc.


8
Как я могу делегировать инструмент, как BC, чтобы поместить ответ в переменную RESULT?
Medya Gh

2
так что вы имеете в виду как VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh

54
@ Шевин VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)или VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")без $ (()) (двойные скобки); который расширяется с помощью bash перед выполнением команды
Науэль Фуйе

4
Правда, но, скорее всего, awk уже установлен в системе.
CMCDragonkai

9
@NahuelFouilleul У вас есть лучший ответ здесь. Должен действительно быть свой ответ и принят в качестве ответа. Эта конкретная строка была невероятно полезной: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
Дэвид

197

ты можешь сделать это:

bc <<< 'scale=2; 100/3'
33.33

ОБНОВЛЕНИЕ 20130926 : вы можете использовать:

bc -l <<< '100/3' # saves a few hits

8
... и добавляет много цифр. По крайней мере, на моей машине это дает 33,33333333333333333333, в то время как первый дает 33,33.
Андреас Шпиндлер

12
@AndreasSpindler Вид старого поста, но в случае, если кто-то захочет узнать, это можно изменить, применив команду масштаба, например. bc -l <<< 'scale=2; 100/3'
Марти

Просто следите, если вы надеетесь получить целое число для последующего использования в bash, используя scale = 0. Начиная с v1.06.95, bc по какой-то причине игнорирует переменную масштаба, когда входные числа имеют десятичную часть. Может быть, это в документах, но я не смог найти это. Попробуйте: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Грег Белл

1
@GregBell На странице руководства написано, Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.что для /оператора есть дополнительная заметка :The scale of the result is the value of the variable scale.
Псмит

2
Спасибо @psmith. Интересно, что для / он говорит, что «масштаб результата - это значение переменной масштаба», но не для умножения. Мои лучшие примеры: bc <<< 'scale=1; 1*3.00001' шкала действительно 5 по какой-то причине, bc <<< 'scale=1; 1/3.000001' шкала 1. Интересно, что деление на 1 bc <<< 'scale=1; 1*3.00001/1'
Грег Белл

136

удар

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

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Вывод:

.33

Смотрите ответ Нильфреда для аналогичного, но более краткого подхода.

альтернативы

Помимо упомянутых bcи awkальтернатив есть также следующее:

CLISP

clisp -x '(/ 1.0 3)'

с убранным выходом:

clisp --quiet -x '(/ 1.0 3)'

или через стандартный ввод:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

Округ Колумбия

echo 2k 1 3 /p | dc

гений кли калькулятор

echo 1/3.0 | genius

Ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

Gnuplot

echo 'pr 1/3.' | gnuplot

JQ

echo 1/3 | jq -nf /dev/stdin

Или:

jq -n 1/3

КШ

echo 'print $(( 1/3. ))' | ksh

Lua

lua -e 'print(1/3)'

или через стандартный ввод:

echo 'print(1/3)' | lua

максима

echo '1/3,numer;' | maxima

с убранным выходом:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

узел

echo 1/3 | node -p

октава

echo 1/3 | octave

Perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

р

echo 1/3 | R --no-save

с убранным выходом:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

Рубин

echo print 1/3.0 | ruby

Wcalc

echo 1/3 | wcalc

С убранным выходом:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

ЗШ

echo 'print $(( 1/3. ))' | zsh

единицы

units 1/3

С компактным выходом:

units --co 1/3

Другие источники

Стефан Шазелас ответил на аналогичный вопрос в Unix.SX.


9
Отличный ответ. Я знаю, что он был опубликован через несколько лет после вопроса, но он заслуживает того, чтобы быть принятым ответом.
Брайан Клайн

2
Если у вас есть zsh, то стоит написать свой сценарий на zsh вместо Bash
Андреа Корбеллини,

Большой палец вверх для gnuplot :)
andywiecko

Хороший список. Особенно echo 1/3 | node -pкоротко.
Джонни Вонг

39

Немного улучшив ответ Марвина:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc не всегда поставляется как установленный пакет.


4
Скрипт awk должен exitпредотвращать чтение из входного потока. Я также предлагаю использовать -vфлаги awk для предотвращения синдрома склонности зубочистки. Итак:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
Aecolley

2
Более awkish способ сделать это будет читать аргументы из входного потока: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bcявляется частью POSIX, он обычно предустановлен.
fuz

это сработало для меня, используя git bash в windows 7 ... спасибо :)
The Beast

31

Вы можете использовать bc по -lопции (буква L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)

4
Если я не включаю в -lмою систему, bc не выполняет математику с плавающей запятой.
Starbeamrainbowlabs

28

В качестве альтернативы bc вы можете использовать awk в вашем скрипте.

Например:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

Выше "% .2f" сообщает функции printf возвращать число с плавающей запятой с двумя цифрами после запятой. Я использовал echo для передачи переменных как полей, так как awk работает с ними корректно. «$ 1» и «$ 2» относятся к первому и второму полям, вводимым в awk.

И вы можете сохранить результат как некоторую другую переменную, используя:

RESULT = `echo ...`

Превосходно! Спасибо. Это полезно для встроенной среды, где bc отсутствует. Вы сэкономили мне немного времени на компиляцию.
энтузиастик

19

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

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Этот пост может вам помочь: bash - Стоит ли переходить на zsh для повседневного использования?


3
Я большой поклонник zsh и использую его последние 4 года, но интерактивное использование - хороший акцент здесь. Скрипт, который требует zsh, обычно не будет очень переносимым на разнородном наборе машин, как это, к сожалению, обычно не стандартно (честно, может быть, все в порядке; OP точно не сказал, как он будет использоваться).
Брайан Клайн

17

Что ж, до float было время, когда использовалась логика с фиксированными десятичными числами:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

Последняя строка - bashim, если не используете bash, попробуйте этот код:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

Обоснование кода: умножить на 100 перед делением, чтобы получить 2 десятичных знака.


5

Если вы нашли вариант вашего предпочтения, вы также можете включить его в функцию.

Здесь я оборачиваю некоторый bashism в функцию div:

Один лайнер:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

Или многострочный:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Теперь у вас есть функция

div <dividend> <divisor> [<precision=2>]

и использовать его как

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

ОБНОВИТЬ Я добавил небольшой скрипт, который предоставляет вам основные операции с числами с плавающей запятой для bash:

Использование:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

А вот и скрипт:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}

1
local _d=${3:-2}проще
KamilCuk

4

Это не совсем с плавающей точкой, но если вам нужно что-то, что устанавливает более одного результата в одном вызове bc ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

вычисляет площадь и диаметр круга, радиус которого указан в $ 1


4

Существуют сценарии, в которых вы не можете использовать bc, потому что он может просто не присутствовать, как в некоторых урезанных версиях busybox или встроенных системах. В любом случае ограничение внешних зависимостей - это всегда хорошая вещь, поэтому вы всегда можете добавить нули к числу, деленному на (числитель), что равносильно умножению на степень 10 (вы должны выбрать степень 10 в соответствии с точность, которая вам нужна), которая сделает вывод деления целым числом. Получив это целое число, обработайте его как строку и поместите десятичную точку (перемещая ее справа налево) число раз, равное десятичной степени, на которую вы умножили числитель. Это простой способ получения результатов с плавающей запятой, используя только целые числа.


1
Даже у Busybox есть Awk. Возможно, здесь должен быть более выдающийся ответ Awk.
tripleee

4

Хотя вы не можете использовать деление с плавающей запятой в Bash, вы можете использовать деление с фиксированной запятой. Все, что вам нужно сделать, это умножить ваши целые числа на степень 10, а затем разделить целую часть и использовать операцию по модулю, чтобы получить дробную часть. Округление по мере необходимости.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))

4

я знаю, что он старый, но слишком заманчивый. Итак, ответ: вы не можете ... но вы можете. давайте попробуем это:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

Таким образом, вы получаете 2 цифры после точки, усеченные (назовите это округлением до нижнего значения, хаха) в чистом bash (не нужно запускать другие процессы). конечно, если вам нужна только одна цифра после точки, вы умножаете на 10 и делаете по модулю 10.

что это делает:

  • первый $ ((...)) делает целочисленное деление;
  • секунда $ ((...)) делает целочисленное деление на что-то в 100 раз больше, по существу сдвигая ваши 2 цифры влево от точки, затем (%) получая только эти 2 цифры, выполняя по модулю.

Бонусный трек : bc version x 1000 занял 1,8 секунды на моем ноутбуке, а чистый bash - 0,016 секунды.


3

Как сделать вычисления с плавающей точкой в ​​Bash:

Вместо использования " здесь строки » ( <<<) с bcкомандой, как это делает один из примеров с наибольшим количеством голосов , вот мой любимый bcпример с плавающей запятой прямо из EXAMPLESраздела справочныхbc страниц (см.man bc справочных страниц Справочные страницы).

Прежде чем мы начнем, знает , что уравнение для пи является: pi = 4*atan(1). a()ниже bcматематическая функция для atan().

  1. Это как сохранить результат вычисления с плавающей запятой в переменную bash - в данном случае в переменнуюpi . Обратите внимание, что scale=10в этом случае количество десятичных знаков точности устанавливается равным 10. Любые десятичные цифры после этого места усекаются .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Теперь, чтобы иметь единственную строку кода, которая также печатает значение этой переменной, просто добавьте echo команду в конец в качестве последующей команды, как показано ниже. Обратите внимание на усечение в 10 десятичных разрядов, как приказано:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Наконец, давайте добавим некоторое округление. Здесь мы будем использовать printfфункцию для округления до 4 десятичных знаков. Обратите внимание, что3.14159... раундов сейчас 3.1416. Поскольку мы округляем, нам больше не нужно использовать scale=10усечение до 10 десятичных знаков, поэтому мы просто удалим эту часть. Вот конечное решение:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Вот еще одно действительно отличное приложение и демонстрация вышеперечисленных методов: измерение и печать во время выполнения.

Обратите внимание, что dt_min округляется от 0.01666666666...до 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Связанный:


1
надежный ответ с подробными примерами и примерами использования, а также с использованием printf
downvoteit


2

Для тех, кто пытается рассчитать проценты с принятым ответом, но теряет точность:

Если вы запустите это:

echo "scale=2; (100/180) * 180" | bc

Вы получаете 99.00только то, что теряет точность.

Если вы запустите это так:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Теперь вы получаете 99.99.

Потому что вы масштабируете только в момент печати.

Обратитесь сюда


1

** Безопасная для инъекций математика с плавающей запятой в bash / shell **

Примечание. Основное внимание в этом ответе уделяется идеям безопасного инъекционного решения для выполнения математических операций в bash (или других оболочках).Конечно, то же самое можно использовать с незначительной настройкой для выполнения расширенной обработки строк и т. Д.

Большинство решений, которые были представлены, конструируют небольшие скриптлеты на лету, используя внешние данные (переменные, файлы, командная строка, переменные среды). Внешний ввод может быть использован для внедрения вредоносного кода в движок, многие из них

Ниже приведено сравнение использования различных языков для выполнения базовых математических вычислений, где результат с плавающей запятой. Он рассчитывает A + B * 0,1 (как с плавающей запятой).

Все попытки решения избежать создания динамических скриптлетов, которые крайне сложно поддерживать, вместо этого они используют статическую программу и передают параметры в назначенную переменную. Они будут безопасно обрабатывать параметры с помощью специальных символов - уменьшая вероятность внедрения кода. Исключением является «BC», который не предоставляет средства ввода / вывода.

Исключением является 'bc', который не обеспечивает никакого ввода / вывода, все данные поступают через программы в stdin, а весь вывод поступает в stdout. Все расчеты выполняются в песочнице, что не допускает побочных эффектов (открытие файлов и т. Д.). Теоретически, инъекция безопасна по конструкции!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"

для python3 с произвольными аргументами: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness

0

вот команда awk: -F = разделитель полей == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.