Calculating Rounded Percentage in Shell Script Without Using "Bc"

Calculating rounded percentage in Shell Script without using bc

Use AWK (no bash-isms):

item=30
total=70
percent=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }")

echo $percent
43

Bash - Calculate Percentage

you can do this using bc -l command.
Eg. echo "100/1024*20" | bc -l gives 1.953125

How to calculate percentage in shell script

Posix-compliant solution using bc:

#!/bin/sh
Percent="$(echo "
scale=2;
a = $DP * 100 / $SDC;
if (a > -1 && a < 0) { print "'"-0"'"; a*=-1; }
else if (a < 1 && a > 0) print 0;
a" | bc)"

That's kind of ugly with all of those special checks for cases when the answer is between -1 and 1 (exclusive) but not zero.

Let's use this Posix-compliant solution using awk instead:

#!/bin/sh
Percent="$(echo "$DP" "$SDC" |awk '{printf "%.2f", $1 * 100 / $2}')"

Z shell can do this natively:

#!/bin/zsh
Percent="$(printf %.2f $(( DP * 100. / SDC )) )"

(The dot is necessary to instruct zsh to use floating point math.)

Native Posix solution using string manipulation (assumes integer inputs):

#!/bin/sh
# # e.g. round down e.g. round up
# # DP=1 SDC=3 DP=2 SDC=3
Percent=$(( DP * 100000 / SDC + 5)) # Percent=33338 Percent=66671
Whole=${Percent%???} # Whole=33 Whole=66
Percent=${Percent#$Whole} # Percent=338 Percent=671
Percent=$Whole.${Percent%?} # Percent=33.33 Percent=66.67

This calculates 1,000 times the desired answer so that we have all the data we need for the final resolution to the hundredths. It adds five so we can properly truncate the thousandths digit. We then define temporary variable $Whole to be just the truncated integer value, we temporarily strip that from $Percent, then we append a dot and the decimals, excluding the thousandths (which we made wrong so we could get the hundredths rounded properly).

BASH: Percentage change - how to calculate it? How to get absolute value without bc?

As you have mentioned that it's not necessary to do this in bash, I would recommend using awk:

awk -v t1="$time1" -v t2="$time2" 'BEGIN{print (t2-t1)/t1 * 100}'

Normally, awk is designed to process files but you can use the BEGIN block to perform calculations without needing to pass any files to it. Shell variables can be passed to it using the -v switch.

If you would like the result to be rounded, you can always use printf:

awk -v t1="$time1" -v t2="$time2" 'BEGIN{printf "%.0f", (t2-t1)/t1 * 100}'

The %.0f format specifier causes the result to be rounded to an integer (floating point with 0 decimal places).

Bash - echo percentage with no decimal point with result returned from bc command

You can try this approach:

echo "Compression ratio: $(bc <<< "scale=2; x = 8 / 7 * 100; scale = 0; x / 1")%";

Output:

Compression ratio: 114%

Calculating grades with different weighted percentages and outputting a final grade in bash

There are several things you can do following on from my comment above. First use the POSIX arithmetic operators (( ... )) instead of the ancient expr. Next, since bash only provides integer math, in order to work with your percentages, you must multiply the percentages by 100 and then divide by 100 to obtain the total. You don't divide by 4, your percentages already account for weighting scores to arrive at 100%. Further, in bash and then you must rely on a floating point utility like bc to handle all floating point calculations, including the division to arrive at your avg.

However, that alone will not remove the rounding error when you go to compare avg in your if then elif ... else statements (e.g. treating 79.9 as 79). Using bc to obtain 79.9 you still need a way to properly handle rounding to obtain 80 from 79.5 (or better) and 79 for anything less than 79.5 but greater than or equal to 78.5.

Thankfully printf -v provides a convenient way to store the results of a conversion as a variable and handle the rounding since it uses the the C-print conversion logic. For example, to save the results of avg converted by bc and then properly rounded (saved in the variable name rounded) you could do:

total=$((s1 * 30 + s2 * 30 + s3 * 10 + s4 * 30))
avg=$((total / 100)) ## not used but saved for output demonstration below
printf -v rounded "%.0f" $(printf "scale=2; $total/100\n" | bc)

Putting it altogether you could do:

#!/bin/bash

echo "What is your name?"
read name
echo "What is your score on the Assignment?"
read s1
echo "What is your score on the Quiz?"
read s2
echo "What is your score on the Midterm Exam?"
read s3
echo "What is your score on the Final Exam?"
read s4

total=$((s1 * 30 + s2 * 30 + s3 * 10 + s4 * 30))
avg=$((total / 100))
printf -v rounded "%.0f" $(printf "scale=2; $total/100\n" | bc)

if [ "$rounded" -ge 80 ]
then
echo "$name's grade is an A"
elif [ "$rounded" -ge 70 ]
then
echo "$name's grade is a B"
elif [ "$rounded" -ge 60 ]
then
echo "$name's grade is a C"
elif [ "$rounded" -ge 50 ]
then
echo "$names's grade is a D"
else
echo "$name's grade is an F"
fi

To confirm the rounding his handled properly, you can add a simply printf following saving rounded, for example add:

printf "\ntotal: %d\navg  : %d\nrounded avg: %d\n\n" \
"$total" "$avg" "$rounded"

Then to confirm, enter scores that would result in a number that if not rounded properly would result in a "B" instead of an "A" (which you liberally give at a score of 80), e.g.

Example Use/Output

$ bash student.sh
What is your name?
John
What is your score on the Assignment?
78
What is your score on the Quiz?
80
What is your score on the Midterm Exam?
81
What is your score on the Final Exam?
81

total: 7980
avg : 79
rounded avg: 80

John's grade is an A

Using a case Statement

You may also want to consider eliminating your long chain of if then elif then elif then ... statement with a simple case ... esac statement that will greatly clean things up. For example you can replace the entire collection of if then elif ... else with

printf "%s's grade is an '" "$name"
case "$rounded" in
100 | [89]? ) echo "A'";;
7? ) echo "B'";;
6? ) echo "C'";;
5? ) echo "D'";;
* ) echo "F'";;
esac

Example Use/Output With case

$ bash student.sh
What is your name?
John
What is your score on the Assignment?
78
What is your score on the Quiz?
80
What is your score on the Midterm Exam?
80
What is your score on the Final Exam?
80

total: 7940
avg : 79
rounded avg: 79

John's grade is an 'B'

Look things over and let me know if you have questions.

division math in a bash script without bc?

use bash's math builtin. it just prints out the integers/whole numbers. so if you want to discard/truncate the decimal places use something like:

echo $(((11*100)/531)) # prints 2

Shell Percentage Calculation With Very Large Numbers

You can use printf to fix your number:

RESULT=$(printf "%.0f" $(echo "scale=8;100/$MAX*$RESULT" | bc))

How to calculate half of total memory rounded up to next 1G boundary?

Assuming everything in G, you need:

free -h | awk 'NR==2{print(int(substr($2,1,length($2)-1)/2+.5))}'

Explanation:

To print the second line, you need

free -h | awk 'NR==2{print}'

To print the second field of the second line, you need

free -h | awk 'NR==2{print($2)}' # prints 7.7G

To remove the final G, you need substring which start at 1st character and contains as many character as in the string, minus 1.

free -h | awk 'NR==2{print(substr($2,1,length($2)-1))}' # prints 7.7

To get half of it, you need

free -h | awk 'NR==2{print(substr($2,1,length($2)-1)/2)}' # prints 3.85

To round to the closest integer, you need to add 0.5 and truncate the fractional part (which is done by int function)

free -h | awk 'NR==2{print(int(substr($2,1,length($2)-1)/2+.5))}' # prints 4


Related Topics



Leave a reply



Submit