1

I have 3 variables and values:

totalLines=14 outsideLines=6 multiplied=600 

totalLines represents the total number of lines (100%), while outsideLines represents number of lines with the timestamp values outside certain limit. My purpose is to calculate the percentage of those outside lines. If I do this:

percentage=$(( multiplied / totalLines)) 

and echo $percentage, I get result:

42 

However, I would like to produce percentage in floating point number, like this:

42.85 

My attempt to implement this:

percentage=$( bc <<< 'scale=2; $multiplied / $totalLines' ) 

failed with the following errors:

(standard_in) 1: illegal character: $ (standard_in) 1: illegal character: $ (standard_in) 1: illegal character: L (standard_in) 1: syntax error 

How should I properly use bc in order to get percentage in floating point number?

3 Answers 3

5

Simple

If all you need to do is (approximate, rounded toward zero) math, you can do it directly in most shells with:

➤ printf '%8.2f\n' "$((multiplied*100/totalLines))e-2" 42.85 

And, in bash or recent versions of zsh or ksh93, you can assign to a variable like this:

➤ printf -v percentage '%.2f\n' "$((multiplied*100/totalLines))e-2" ➤ echo "$percentage" 42.85 

That is a percentage, rounded towards zero, with integer arithmetic, two decimals and no spaces.

That is because the shell does integer math, but we can always calculate the value times 100. That will make the result of $((…)) the integer 4285¹.

All that is required then is to move the decimal separator (dot) two positions left.

That is the reason for e-2, the value will be 4285e-2.
And then, format the result as a float number with two decimals %8.2f.

A more portable option but slightly slower (a sub-shell implies a fork()), in most shells, use:

percentage="$(printf '%8.2f\n' "$((outsideLines*100*100/totalLines))e-2")" 

This is all done inside the shell (if the printf is a built-in).
Not needed to load any external applications. That makes this faster, and with less load on the CPU (for one number).

Don't try to scale this to 1000 numbers, the shell is not the correct tool to process text files.

Nearest

If you do need to get percentage rounded to nearest-with-ties-to-even. The default rounding method for IEEE-754, we need to use more digits on the fractional part. We can get some more digits, 13 (11 fractional digits) in this case, with:

$ printf '%.2f\n' "$((outsideLines*10**15/totalLines))e-13" 62.75 $ printf '%.4f\n' "$((outsideLines*10**15/totalLines))e-13" 62.7451 $ printf '%.6f\n' "$((outsideLines*10**15/totalLines))e-13" 62.745098 

It makes no sense to try to extract more than 11 fractional digits from the integer math of the shell. A double float is limited to 53 bits (generally) which will become no more than 16 decimal digits. Subtract 2 digits as they are used for the integer part of the percentage number and we are left with no more than 14 possible fractional digits. In small percentages, that gets reduced (as the math was executed over integer values), so, probably no more than 11 fractional digits could be trusted.

bc

If you need more precision (very small percentages), it makes no sense to use the integer math of the shell. In such cases, use bc directly (with the associated slow down of loading and executing an external program).

$ bc <<<"scale=30; outsidelines=$outsideLines; totallines=$totalLines; outsidelines*100/totallines" 42.857142857142857142857142857142 $ val="$(bc <<<"scale=30; outsidelines=$outsideLines; totallines=$totalLines; outsidelines*100/totallines")" $ printf '%.8f' "$val" 42.85714286 $ printf '%.6f' "$val" 42.857143 $ printf '%.4f' "$val" 42.8571 $ printf '%.2f' "$val" 42.86 

¹ Shell integer arithmetic rounds toward zero. Here, 42.85714285714285... is truncated to 42.85 removing the 0.00714285714285.

² Rounded by the default rounding mode in your OS, which generally is round to nearest with ties to even (banker's rounding (but might be nearest-with-ties-away-from-zero in some OS). That is done with the 11 fractional digits used. As the value calculated is closer to 42.86 for two digits, for example, that is what gets printed.

0
2

You can do this:

percentage=$(echo "scale=2; $multiplied / $totalLines" | bc) 
1

Thanks to @Stéphane Chazelas for pointing out my misuse of variable


Just a little completion of @user2323 's answer:

> printf '%.2f\n' "$((outsideLines*100*100/totalLines))e-2" > 42.85 

Since 'e-2' is appended at the end, it should add one more 100 at the beginning. "$((outsideLines*100/totalLines))" will only get the integer part of percentage:

> printf '%.2f\n' "$((outsideLines*100/totalLines))" > 42 

, and "$((outsideLines*100/totalLines))e-2" get a float number with two decimals, but not the percentage:

> printf '%.2f\n' "$((outsideLines*100/totalLines))e-2" > 0.42 
2

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.