Как округлить десятичные дроби, используя bc в bash?


45

Быстрый пример того, что я хочу, используя bash-скриптинг:

#!/bin/bash
echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
echo "scale=2; $float/1.18" |bc -l
read -p "Press any key to continue..."
bash scriptname.sh

Предполагая, что цена: 48,86 Ответ будет: 41,406779661 (41,40 на самом деле, потому что я использую scale=2;)

Мой вопрос: как мне округлить второе десятичное число, чтобы показать ответ таким образом ?: 41.41


Я нахожу это странным, потому что "printf"% 0.2f \ n "41.445" теперь работает, но "printf"% 0.2f \ n "41.435 и printf"% 0.2f \ n "41.455" работают. Даже ваш собственный случай работает (12.04), но не с .445
Луис Альварадо

8
ИМХО, никто не ответил на этот вопрос удовлетворительно , возможно, потому, что bcне может достичь того, что запрашивается (или, по крайней мере, вопрос, который я задавал, когда нашел этот пост), а именно, как округлить десятичные дроби с помощьюbc ( который вызывается bash).
Адам Кац

Я знаю, что этот вопрос старый, но я не могу удержаться от комментариев, выдвигающих мою собственную реализацию bc: github.com/gavinhoward/bc . Для этого он находится во встроенной библиотеке с r()функцией, так что вы можете использовать bc -le "r($float/1.18, 2)".
Гэвин Ховард

Ответы:


31

Функция bash round:

round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

Используется в вашем примере кода:

#!/bin/bash
# the function "round()" was taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# the round function:
round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
#echo "scale=2; $float/1.18" |bc -l
echo $(round $float/1.18 2);
read -p "Press any key to continue..."

Удачи: о)


1
Кстати, если число отрицательное, мы должны использовать-0.5
Водолей Power

Не удалось заставить пример работать на консоли тестирования! «Отладка» немного показала, что, например, $2 = 3мне пришлось использовать echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Обратите внимание на env перед printf! Это снова научит вас, что всегда важно понимать, что вы копируете из других мест.
синтаксическая ошибка

@ Aquarius Power спасибо за вдохновение! Я теперь разветвил версию вышеуказанного скрипта, которая будет работать как с отрицательными, так и с положительными числами.
синтаксическая ошибка

Это излишне сложно и делает все виды дополнительной арифметики, чтобы получить более простые ответы, предоставленные migas и zuberuber.
Адам Кац

@ AquariusPower +1 да, это отрицательно для отрицательных чисел. Чтобы проверить отрицательное (не целое число!) Число, я попытался немного и согласился на if [ эхо "$ 1/1" | bc` -gt 0] `- есть ли более элегантный способ проверки (кроме парсинга строки для« - »)?
RobertG

30

Самое простое решение:

printf %.2f $(echo "$float/1.18" | bc -l)

2
Как отмечалось также в комментарии выше ( askubuntu.com/a/179949/512213 ), к сожалению, это будет вести себя некорректно для отрицательных чисел.
RobertG

2
@ РобертГ: Почему? Это решение не использует +0.5. Попробуй printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"и получишь 41.41и -41.41.
Musiphil

1
Также возможно использование echo "$float/1.18" | bc -l | xargs printf %.2f
десерт

Решение в этом ответе дает мне: в bash: printf: 1.69491525423728813559: invalid numberзависимости от локали.
Торстен Бронджер

19

Округление Bash / awk:

echo "23.49" | awk '{printf("%d\n",$1 + 0.5)}'  

Если у вас есть Python, вы можете использовать что-то вроде этого:

echo "4.678923" | python -c "print round(float(raw_input()))"

Спасибо за совет, но он не решает, что мне нужно. Я должен сделать это, просто используя bash ...
blackedx

1
Команда python более удобна для чтения и хороша для быстрых сценариев. Также поддерживает произвольное округление цифр, добавив, например, «3» в функцию округления. Спасибо
Elle

echo "$float" |awk '{printf "%.2f", $1/1.18}'выполнит запрошенную математику вопроса с запрошенным разрешением сотых долей. Это столько же "использование bash", сколько bcвызов в вопросе.
Адам Кац

2
Для Python вы можете просто использовать что-то вроде python -c "print(round($num))"где num=4.678923. Нет необходимости бродить с stdin. Вы можете также круглые с п цифр нравится так: python -c "print(round($num, $n))".
шесть

Мне удалось сделать что-то довольно аккуратное с этим решением, моя проблема была 0,5, в этом случае я не всегда получал нужный мне раунд, поэтому я придумал следующее: echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(Когда + округляется вверх и - округляется вниз).
Ярон

4

Вот чисто до н.э. Правила округления: +/- 0,5, округление от нуля.

Поместите искомую шкалу в $ result_scale; ваша математика должна быть там, где $ MATH находится в списке команд bc:

bc <<MATH
h=0
scale=0

/* the magnitude of the result scale */
t=(10 ^ $result_scale)

/* work with an extra digit */
scale=$result_scale + 1

/* your math into var: m */
m=($MATH)

/* rounding and output */
if (m < 0) h=-0.5
if (m > 0) h=0.5

a=(m * t + h)

scale=$result_scale
a / t
MATH

1
очень интересно! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear: 5, как здесь объяснено :)
Водолей Сила

Я назначаю это как «лучший ответ».
Марк Тамский

2

Я знаю, что это старый вопрос, но у меня есть чистое 'bc' -решение без 'if' или ответвлений:

#!/bin/sh
bcr()
{
    echo "scale=$2+1;t=$1;scale-=1;(t*10^scale+((t>0)-(t<0))/2)/10^scale" | bc -l
}

Используйте это как bcr '2/3' 5или bcr '0.666666' 2-> (выражение, сопровождаемое масштабом)

Это возможно, потому что в bc (например, C / C ++) разрешено смешивать логические выражения в ваших вычислениях. Выражение ((t>0)-(t<0))/2)будет оцениваться до +/- 0,5 в зависимости от знака 't' и поэтому будет использовать правильное значение для округления.


Это выглядит аккуратно, но bcr "5 / 2" 0возвращается 2вместо 3. Я что-то пропустил?
Блюджей

1
#!/bin/bash
# - loosely based on the function "round()", taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# - inspired by user85321 @ askubuntu.com (original author)
#   and Aquarius Power

# the round function (alternate approach):

round2()
{
    v=$1
    vorig=$v
    # if negative, negate value ...
    (( $(bc <<<"$v < 0") == 1 )) && v=$(bc <<<"$v * -1")
    r=$(bc <<<"scale=$3;(((10^$3)*$v/$2)+0.5)/(10^$3)")

    # ... however, since value was only negated to get correct rounding, we 
    # have to add the minus sign again for the resulting value ...

    (( $(bc <<< "$vorig < 0") == 1 )) && r=$(bc <<< "$r * -1")
    env printf %.$3f $r
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
round2 $float 1.18 2
echo && read -p "Press any key to continue..."

Это на самом деле просто: нет необходимости явно добавлять жестко закодированный вариант "-0.5" для отрицательных чисел. Говоря математически, мы просто вычислим абсолютное значение аргумента и добавим 0.5, как обычно. Но так как у нас (к сожалению) нет встроенной abs()функции (если мы не кодируем ее), мы просто отменим аргумент, если он отрицательный.

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


@muru о ваших последних изменениях: здесь вместо строк echo .. |, арифметика оболочки, какой смысл echo $()? Это легко объяснить: ваши строки здесь всегда будут требовать доступной для записи/tmp директории! И мое решение также будет работать на только для чтения окружающей среды, например , аварийно корневой оболочки , где /это не всегда доступен для записи по умолчанию. Так что была веская причина, почему я так закодировал.
syntaxerror

Herestrings, я согласен. Но когда это echo $()нужно? Это и отступы побудили меня отредактировать, вот что произошло.
Муру

@muru Ну, самый бессмысленный случай, echo $()когда нужно было исправить, был на самом деле предпоследней строкой, смеется. Спасибо за хедз-ап. По крайней мере, теперь это выглядит намного лучше, я не могу отрицать. В любом случае, я любил логику в этом. Множество логических вещей, менее лишних «если» (что наверняка взорвет код на 50 процентов).
syntaxerror

1

Я все еще ищу чистый bcответ о том, как округлить только одно значение в функции, но вот чистый bashответ:

#!/bin/bash

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"

embiggen() {
  local int precision fraction=""
  if [ "$1" != "${1#*.}" ]; then  # there is a decimal point
    fraction="${1#*.}"       # just the digits after the dot
  fi
  int="${1%.*}"              # the float as a truncated integer
  precision="${#fraction}"   # the number of fractional digits
  echo $(( 10**10 * $int$fraction / 10**$precision ))
}

# round down if negative
if [ "$float" != "${float#-}" ]
  then round="-5000000000"
  else round="5000000000"
fi

# calculate rounded answer (sans decimal point)
answer=$(( ( `embiggen $float` * 100 + $round ) / `embiggen 1.18` ))

int=${answer%??}  # the answer as a truncated integer

echo $int.${answer#$int}  # reassemble with correct precision

read -p "Press any key to continue..."

По сути, это тщательно извлекает десятичные дроби, умножает все на 100 миллиардов (10¹⁰, 10**10in bash), корректирует точность и округление, выполняет фактическое деление, делит обратно до соответствующей величины, а затем повторно вводит десятичное число.

Шаг за шагом:

В embiggen()функции присваивает усечено целым формы его аргумента $intи сохраняет число после точки в $fraction. Количество дробных цифр указано в $precision. В математике умножает 10¹⁰ по конкатенации $intи , $fractionа затем корректирует , что соответствует точности (например , embiggen 48.86становится 10¹⁰ × 4886/100 и возврат 488600000000которых является 488600000000).

Нам нужна конечная точность в сотых долях, поэтому мы умножаем первое число на 100, добавляем 5 для округления и затем делим второе число. Это назначение $answerоставляет нам в сто раз окончательный ответ.

Теперь нам нужно добавить десятичную точку. Мы присваиваем новое $intзначение $answerисключению двух последних цифр, затем echoоно ставится точкой и $answerисключает $intзначение, о котором уже позаботились. (Не берите в голову ошибку подсветки синтаксиса, которая заставляет это походить на комментарий)

(Bashism: возведение в степень не является POSIX, так что это bashism. Чистое решение POSIX потребовало бы циклов для добавления нулей, а не использования степеней десяти. Кроме того, «embiggen» - совершенно непонятное слово.)


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

printf %.2f $((float/1.18))

(Я бы хотел, чтобы кто-нибудь добавил комментарий к этому ответу с хитростью, чтобы включить арифметику с плавающей запятой bash, но я почти уверен, что такой функции еще не существует.)


1

если у вас есть результат, например, рассмотрите 2.3747888

все, что вам нужно сделать, это:

d=$(echo "(2.3747888+0.5)/1" | bc); echo $d

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

(2.49999 + 0.5)/1 = 2.99999 

десятичные дроби удаляются, bcи поэтому округляется до 2, как это должно быть


1

Мне пришлось рассчитать общую продолжительность коллекции аудиофайлов.

Поэтому мне пришлось:

A. получить продолжительность для каждого файла ( не показан )

B. сложите все длительности (они были в NNN.NNNNNN(fp) секундах)

C. отдельные часы, минуты, секунды, секунды.

D. Выведите строку HR: MIN: SEC: FRAMES, где frame = 1/75 сек.

(Кадры взяты из кода SMPTE, используемого в студиях.)


A: использовать ffprobeи анализировать строку продолжительности в число fp (не показано)

B:

 # add them up as a series of strings separated by "+" and send it to bc

arr=( "${total[@]}" )  # copy array

# IFS is "Internal Field Separator"
# the * in arr[*] means "all of arr separated by IFS" 
# must have been made for this
IFS='+' sum=$(echo "scale=3; ${arr[*]} "| bc -l)# (-l= libmath for fp)
echo $sum 

C:

# subtract each amount of time from tt and store it    
tt=$sum   # tt is a running var (fp)


hrs=$(echo "$tt / 3600" | bc)

tt=$(echo "$tt - ( $hrs * 3600 )" | bc )

min=$(echo "$tt / 60" | bc )

tt=$(echo "$tt - ($min *60)" | bc )

sec=$(echo "$tt/1" | bc )

tt=$(echo "$tt - $sec" | bc )

frames=$(echo "$tt * 75"  | bc ) # 75 frames /sec 
frames=$(echo "$frames/1" | bc ) # truncate to whole #

D:

#convert to proper format with printf (bash builtin)        
hrs=$(printf "%02d\n" $hrs)  # format 1 -> 01 

min=$(printf "%02d\n" $min)

sec=$(printf "%02d\n" $sec)

frames=$(printf "%02d\n" $frames)

timecode="$hrs:$min:$sec:$frames"

# timecode "01:13:34:54"

1

Вот сокращенная версия вашего скрипта, исправленная для предоставления желаемого результата:

#!/bin/bash
float=48.86
echo "You asked for $float; This is the price without taxes:"
echo "scale=3; price=$float/1.18 +.005; scale=2; price/1 " | bc

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

Кроме того, масштабный коэффициент применяется во время работы; Итак (это bcкоманды, вы можете вставить их в свой терминал):

float=48.86; rate=1.18; 
scale=2; p2=float/rate
scale=3; p3=float/rate
scale=4; p4=float/rate
print "Compare:  ",p2, " v ", p3, " v ", p4
Compare:  41.40 v 41.406 v 41.4067

# however, scale does not affect an entered value (nor addition)
scale=0
a=.005
9/10
0
9/10+a
.005

# let's try rounding
scale=2
p2+a
41.405
p3+a
41.411
(p2+a)/1
41.40
(p3+a)/1
41.41

1

Чистая реализация до нашей эры в соответствии с просьбой

define ceil(x) { auto os,xx;x=-x;os=scale;scale=0 xx=x/1;if(xx>x).=xx-- scale=os;return(-xx) }

если вы поместите это в файл с именем functions.bc, то вы можете округлить с

echo 'ceil(3.1415)' | bc functions.bc

Код для реализации bc находится по адресу http://phodd.net/gnu-bc/code/funcs.bc


0
bc() {
        while :
        do
                while IFS='$\n' read i
                do
                        /usr/bin/bc <<< "scale=2; $i" | sed 's/^\./0&/'
                done
        done
}

0

Вы можете использовать awk, трубы не нужны:

$> PRICE=48.86
$> awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}"
41.41

Заранее установите цену с помощью переменной среды PRICE=<val>.

  • Затем вызовите awk - $PRICEвойдет в awk как переменная awk price.

  • Затем округляется до ближайшего сотого с +.005.

  • Параметр форматирования printf %.2fограничивает масштаб до двух десятичных знаков.

Если вам нужно сделать это много, то этот способ намного эффективнее, чем выбранный ответ:

time for a in {1..1000}; do echo $(round 48.86/1.18 2) > /dev/random; done
real    0m8.945s
user    0m6.067s
sys 0m4.277s

time for a in {1..1000}; do awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}">/dev/random; done
real    0m2.628s
user    0m1.462s
sys 0m1.275s
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.