Why rounding error in bash? - bash

5+50*3/20 + (19*2)/7 = 17.9285714286
= 17.929
I want to get the exact value truncated to 3 decimal places, that is 17.929 by doing the following bash operation.
echo " scale = 3; 5+50*3/20 + (19*2)/7 " | bc
But it gives me the value 17.928.
$ echo " scale = 3; 5+50*3/20 + (10*9)/7 " | bc
17.928
What Can I do??
N.B.: This is a Hackerrank challenge. Even it's not giving correct output in their console.

bc is ceiling the decimal value rather than rounding off. You can use awk:
awk 'BEGIN{printf "%.3f\n", 5+50*3/20 + (19*2)/7}'
17.929

When you use scale=3 in bc, you're not only specifying the number of decimal places in the output, you're limiting the number of decimal places that bc is using to do its calculations.
To get the correct output, use printf with bc:
$ printf '%.3f\n' "$(bc -l <<<'5+50*3/20 + (19*2)/7')"
17.929
If the expression is inside a variable $var, replace the string in single quotes with "$var".

Related

addition in bash with decimal using bc

I'm new in bash and i'd like to know why my script doesn t work the way i'd like it work..
I have this bash script:
#!/bin/bash
read n
var=($(cat))
bim=${var[*]}
toto=$(echo $bim | sed 's/ /+/g' | bc)
echo $toto
bobo=$(($toto/$n | bc -l))
echo $bobo | awk '{printf "%.3f\n", $1}'
This is supposed to add up all the values that "cat" has stored in an array and divide the total by the first value that "read" reads. And the result should return me a decimal value of three decimal places. However, it only returns a round number to me when I use bc -l! And when I use awk '{printf% .3f ", $ 1}' it prints .000!
Do you know why?
Thanks
bash only does integer arithmetics and the arithmetic expansion
bobo=$(($toto/$n | bc -l))
will not do what you think. The pipe sign in | bc -l is not a pipe sign. It's a bitwise OR. Broken down:
$toto is interpreted as a variable (as would toto)
$n is interpreted as a variable (as would n)
| is bitwise OR
bc is interpreted as a variable (with the value 0)
- is interpreted as a minus sign (in an arithmetic expression)
l is interpreted as a variable (with the value 0)
So, it becomes $toto/$n | 0 -0 which is the same as the integer division toto/n ($ is not needed for variables in arithmetic expansions).
You could instead use bc for the division too, but you need to set the scale in bc. Here's an example setting it to 3 before performing the division:
bobo=$(echo "scale=3;$toto/$n" | bc)
Note that you don't need to echo this through awk.
Just echo $bobo and you should get the result you want.
Example input:
3
11.3
9
8
Output after having applied the suggested changes:
28.3
9.433

Generate uniform random number in range of floats in bash [duplicate]

This question already has answers here:
Random numbers generation with awk in BASH shell
(3 answers)
Closed 2 years ago.
[SOLVED]
I want to generate a uniform random float number in the range of float numbers in the bash script. range e.g. [3.556,6.563]
basically, I am creating LSH(Latin hypercube sampling) function in bash. There I would like to generate an array as one can do with this python command line.
p = np.random.uniform(low=l_lim, high=u_lim, size=[n]).
sample code :
lhs(){
l_lim=($(seq $1 $2 $(echo $3 - $dif | bc)))
h_lim=($(seq $(echo $1 + $dif | bc) $2 $3))
points=()
for ((i=0;i<$n;i++)) ; do
di=${l_lim[i]}
dj=${h_lim[i]}
echo $di, $dj
p=$(awk -v min=6.50 -v max=8.45 -v seed=$RANDOM 'BEGIN{srand(seed);print min+rand()*int(1000*(max-min)+1)/1000}')
points+=("${p}")
done
}
n=5
a=(3 5)
b=(1 3)
dif=$(div $(echo ${a[1]} - ${a[0]} | bc) $n)
lhs ${a[0]} 0.45 ${a[1]}
echo ${points[#]}
I have tried $RANDOM, awk but it did not work for me. I do not want to use python -c.
Most common rand() implementations at least generate a number in the range [0...1), which is really all you need. You can scale a random number in one range to a number in another using the techniques outlined in the answers to this question, eg:
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
For bash you have two choices: integer arithmetic or use a different tool.
Some of your choices for tools that support float arithmetic from the command-line include:
a different shell (eg, zsh)
perl: my $x = $minimum + rand($maximum - $minimum);
ruby: x = min + rand * (max-min)
awk: awk -v min=3 -v max=17 'BEGIN{srand(); print min+rand()*int(1000*(max-min)+1)/1000}'
note: The original answer this was copied from is broken; the above is a slight modification to help correct the problem.
bc: printf '%s\n' $(echo "scale=8; $RANDOM/32768" | bc )
... to name a few.

Floating-point division in bash

I'm trying to convert whatever numbers the user inputs into 2 decimal places.
For instance
What is the total cost in cents? 2345
output: 23.45
this is the code i have so far
percentage=20 #cannot change numerical value must convert into 0.20
echo -n "What is the total cost? ";
read cost_in_cents
echo "scale 1; $cost_in_cents" | bc
I'm also going to be doing some multiplication with percentage, how can i also convert the percentage into a float (0.20)
awk to the rescue!
you can define your own floating point calculator with awk, e.g.
$ calc() { awk "BEGIN{ printf \"%.2f\n\", $* }"; }
now you can call
$ calc 43*20/100
which will return
8.60
Perhaps it's nostalgia for reverse polish notation desk calculators, but I'd use dc rather than bc here:
dc <<<"2 k $cost_in_cents 100 / p"
Output is, properly, a float (with two digits past the decimal point of precision).
The exact same code, with no changes whatsoever, will work to convert 20 to .20.
See BashFAQ #22 for a full discussion on floating-point math in bash.
Bash itself could not process floats.
It can, however, printf them:
$ printf 'value: %06.2f\n' 23.45
value: 023.45
So, you need an external program to do the math:
$ echo "scale=4;2345/100*20/100" | bc
4.6900
Or, equivalent:
$ bc <<<"scale=4;2345*20/10^4"
4.6900
Then, you can format the float with printf:
$ printf 'result: %06.2f\n' $(bc <<<"scale=4;2345*20/10^4")
result: 004.69
Or you can use a program that can process floats; like awk.
How about this:
read -p "What is the total cost? " input
percent=20
echo "scale=2; $input / 100 * $percent / 100" | bc
# input = 2345 , output = 4.69

How to round a floating point number upto 3 digits after decimal point in bash

I am a new bash learner. I want to print the result of an expression given as input having 3 digits after decimal point with rounding if needed.
I can use the following code, but it does not round. Say if I give 5+50*3/20 + (19*2)/7 as input for the following code, the given output is 17.928. Actual result is 17.92857.... So, it is truncating instead of rounding. I want to round it, that means the output should be 17.929. My code:
read a
echo "scale = 3; $a" | bc -l
Equivalent C++ code can be(in main function):
float a = 5+50*3.0/20.0 + (19*2.0)/7.0;
cout<<setprecision(3)<<fixed<<a<<endl;
What about
a=`echo "5+50*3/20 + (19*2)/7" | bc -l`
a_rounded=`printf "%.3f" $a`
echo "a = $a"
echo "a_rounded = $a_rounded"
which outputs
a = 17.92857142857142857142
a_rounded = 17.929
?
You can use awk:
awk 'BEGIN{printf "%.3f\n", (5+50*3/20 + (19*2)/7)}'
17.929
%.3f output format will round up the number to 3 decimal points.
Try using this:
Here bc will provide the bash the functionality of caluculator and -l will read every single one in string and finally we are printing only three decimals at end
read num
echo $num | bc -l | xargs printf "%.3f"

How do I get bc(1) to print the leading zero?

I do something like the following in a Makefile:
echo "0.1 + 0.1" | bc
(in the real file the numbers are dynamic, of course)
It prints .2 but I want it to print 0.2.
I would like to do this without resorting to sed but I can't seem to find how to get bc to print the zero. Or is bc just not able to do this?
You can also resort to awk to format:
echo "0.1 + 0.1" | bc | awk '{printf "%f", $0}'
or with awk itself doing the math:
echo "0.1 0.1" | awk '{printf "%f", $1 + $2}'
This might work for you:
echo "x=0.1 + 0.1; if(x<1) print 0; x" | bc
After a quick look at the source (see bc_out_num(), line 1461), I don't see an obvious way to make the leading 0 get printed if the integer portion is 0. Unless I missed something, this behaviour is not dependent on a parameter which can be changed using command-line flag.
Short answer: no, I don't think there's a way to make bc print numbers the way you want.
I don't see anything wrong with using sed if you still want to use bc. The following doesn't look that ghastly, IMHO:
[me#home]$ echo "0.1 + 0.1" | bc | sed 's/^\./0./'
0.2
If you really want to avoid sed, both eljunior's and choroba's suggestions are pretty neat, but they require value-dependent tweaking to avoid trailing zeros. That may or may not be an issue for you.
I cannot find anything about output format in the documentation. Instead of sed, you can also reach for printf:
printf '%3.1f\n' $(bc<<<0.1+0.1)
echo "$a / $b" | bc -l | sed -e 's/^-\./-0./' -e 's/^\./0./'
This should work for all cases where the results are:
"-.123"
".123"
"-1.23"
"1.23"
Explanation:
For everything that only starts with -., replace -. with -0.
For everything that only starts with ., replace . with 0.
Building on potongs answer,
For fractional results:
echo "x=0.1 + 0.1; if(x<1 && x > 0) print 0; x" | bc -l
Note that negative results will not be displayed correctly. Aquarius Power has a solution for that.
$ bc -l <<< 'x=-1/2; if (length (x) == scale (x) && x != 0) { if (x < 0) print "-",0,-x else print 0,x } else print x'
This one is pure bc. It detects the leading zero by comparing the result of the length with the scale of the expression. It works on both positive and negative number.
This one will also handle negative numbers:
echo "0.1 - 0.3" | bc | sed -r 's/^(-?)\./\10./'
For positive numbers, it may be as simple as printing (an string) zero:
$ echo '"0";0.1+0.1' | bc
0.2
avoid the zero if the number is bigger (or equal) to 1:
$ echo 'x=0.1+0.1; if(x<1){"0"}; x' | bc
0.2
It gets a bit more complex if the number may be negative:
echo 'x= 0.3 - 0.5 ; s=1;if(x<0){s=-1};x*=s;if(s<0){"-"};if(x<1) {"0"};x' | bc
-0.2
You may define a function and add it to a library:
$ echo 'define leadzero(x){auto s;
s=1;if(x<0){s=-1};x*=s;if(s<0){"-"};if(x<1){"0"};
return(x)};
leadzero(2.1-12.4)' | bc
-10.3
$ echo 'define leadzero(x){auto s;
s=1;if(x<0){s=-1};x*=s;if(s<0){"-"};if(x<1){"0"};
return(x)};
leadzero(0.1-0.4)' | bc
-0.3
Probably, bc isn't really the best "bench calculator" for the modern age. Other languages will give you more control. Here are working examples that print values in the range (-1.0..+1.0) with a leading zero. These examples use bc, AWK, and Python 3, along with Here String syntax.
#!/bin/bash
echo "using bc"
time for (( i=-2; i<=+2; i++ ))
{
echo $(bc<<<"scale=1; x=$i/2; if (x==0||x<=-1||x>=1) { print x } else { if (x<0) { print \"-0\";-x } else { print \"0\";x } } ")
}
echo
echo "using awk"
time for (( i=-2; i<=+2; i++ ))
{
echo $(echo|awk "{printf \"%.1f\",$i/2}")
}
echo
echo "using Python"
time for (( i=-2; i<=+2; i++ ))
{
echo $(python3<<<"print($i/2)")
}
Note that the Python version is about 10x slower, if that matters (still very fast for most purposes).
Doing any non-trivial math with sh or bc is a fool's errand. There are much better bench calculators available nowadays. For example, you can embed and execute Python subroutines inside your Bash scripts using Here Documents.
function mathformatdemo {
python3<<SCRIPT
import sys
from math import *
x=${1} ## capture the parameter from the shell
if -1<=x<=+1:
#print("debug: "+str(x),file=sys.stderr)
y=2*asin(x)
print("2*asin({:2.0f})={:+6.2f}".format(x,y))
else: print("domain err")
SCRIPT
}
echo "using Python via Here-doc"
time for (( i=-2; i<=+2; i++ ))
{
echo $(mathformatdemo $i)
}
Output:
using Python via Here-doc
domain err
2*asin(-1)= -3.14
2*asin( 0)= +0.00
2*asin( 1)= +3.14
domain err
this only uses bc, and works with negative numbers:
bc <<< "x=-.1; if(x==0) print \"0.0\" else if(x>0 && x<1) print 0,x else if(x>-1 && x<0) print \"-0\",-x else print x";
try it with:
for y in "0" "0.1" "-0.1" "1.1" "-1.1"; do
bc <<< "x=$y; if(x==0) print \"0.0\" else if(x>0 && x<1) print 0,x else if(x>-1 && x<0) print \"-0\",-x else print x";
echo;
done
Another simple way, similar to one of the posts in this thread here:
echo 'x=0.1+0.1; print "0",x,"\n"' | bc
Print the list of variables, including the leading 0 and the newline.
Since you have the question tagged [bash] you can simply compute the answer and save it to a variable using command substitution (e.g. r="$(...)") and then using [[..]] with =~ to test if the first character in the result is [1-9] (e.g. [[ $r =~ ^[1-9].*$ ]]), and if the first character isn't, prepend '0' to the beginning of r, e.g.
r=$(echo "0.1 + 0.1" | bc) # compute / save result
[[ $r =~ ^[1-9].*$ ]] || r="0$r" # test 1st char [1-9] or prepend 0
echo "$r" # output result
Result
0.2
If the result r is 1.0 or greater, then no zero is prepended, e.g. (as a 1-liner)
$ r=$(echo "0.8 + 0.6" | bc); [[ $r =~ ^[1-9].*$ ]] || r="0$r"; echo "$r"
1.4

Resources