Як округлити десяткові знаки за допомогою 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

Баш-кругла функція:

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
btw, якщо число негативне, ми маємо використовувати-0.5
Водолій Сила

Не вдалось отримати приклад для роботи на тестовій консолі! "Налагодження" трохи виявило, що, наприклад, $2 = 3мені довелося використовувати echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc)). Майте на увазі env перед printf! Це знову навчить вас, що завжди важливо зрозуміти, що ви копіюєте з іншого місця.
синтаксис-помилка

@Aquarius Power дякую за натхнення! Зараз я розклав версію вищезазначеного сценарію, яка буде працювати як з негативними, так і з позитивними цифрами.
синтаксис-помилка

Це зайво складно і робить усілякі зайві арифметики, щоб дійти до більш простих відповідей, які надають міга та зубербер.
Адам Кац

@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
@RobertG: Чому? Це рішення не використовується +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)}'  

Якщо у вас є пітон, ви можете використовувати щось подібне:

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

Ось суто bc рішення. Правила округлення: при +/- 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" без "якщо" або гілок:

#!/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 про ваші останні редагування: замість herestrings echo .. |, арифметичні оболонки, у чому сенс echo $()? Це легко пояснити: ваші рядки тут завжди потребують записуваного/tmp каталогу! І моє рішення також буде працювати на тільки для читання навколишнього середовища, наприклад , аварійно кореневої оболонки , де /це НЕ завжди доступний для запису за замовчуванням. Тож була вагома причина, чому я кодував це саме так.
синтаксичний помилок

Герестрінгс, я згоден. Але коли це echo $()колись потрібно? Це і відступ спонукали мої зміни, герестини просто відбулися.
муру

@muru Ну, найбільш безглуздий випадок, echo $()який було прострочено, щоб виправитись, був насправді передостанньою лінією, lol. Дякую за голову. Принаймні, це виглядає набагато краще, я не можу заперечити. У будь-якому випадку я любив логіку в цьому. Багато булевих матеріалів, менш зайвих "if" s (що, безумовно, підірве код на 50 відсотків).
синтаксичний помилок

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**10в bash), підлаштовується для точності і округлення, преформ фактичного поділу, ділить назад відповідному величині, а потім повторно вставляє десяткову.

Крок за кроком:

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

Ми хочемо остаточної точності сотих, тому множимо перше число на 100, додаємо 5 для цілей округлення, а потім ділимо друге число. Це завдання $answerзалишає нас у стократно остаточній відповіді.

Тепер нам потрібно додати десяткову точку. Нове $intзначення ми присвоюємо $answerвиключенню останніх двох цифр, потім - echoз крапкою та $answerвиключенням $intзначення, про яке вже було зроблено. (Незважаючи на помилку підсвічування синтаксису, яка робить це схожим на коментар)

(Башизм: експоненція - це не POSIX, тому це башізм. Чисте рішення 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, де кадр = 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

Чиста реалізація bc за потребою

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

якщо ви помістите це у файл під назвою function.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.

  • Потім вона округляється до найближчої 100-ї з +.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.