2

Following code calculates the Binomial Probability of a success event k out of n trials:

n=144
prob=$(echo "0.0139" | bc)

echo -e "Enter no.:" 
read passedno

k=$passedno
nCk2() {
    num=1
    den=1
    for((i = 1; i <= $2; ++i)); do
        ((num *= $1 + 1 - i)) && ((den *= i))
    done
    echo $((num / den))
}

binomcoef=$(nCk2 $n $k)

binprobab=$(echo "scale=8; $binomcoef*($prob^$k)*((1-$prob)^($n-$k))" | bc)

echo $binprobab

When for $passedno (=k) "5" is entered, then the result is shown as 0 (instead of "0.03566482") whereas with "4" passed I get ".07261898".

How can I print the output with given precision of 8 decimal digits without getting the rounded value of the output?

1

FWIW,

prob=$(echo "0.0139" | bc)

is unnecessary - you can just do

prob=0.0139

Eg,

$ prob=0.0139; echo "scale=5;1/$prob" | bc
71.94244

There's another problem with your code, apart from the underflow issue. Bash arithmetic may not be adequate to handle the large numbers in your nCk2 function. Eg, on a 32 bit system passing 10 to that function returns a negative number, -133461297271.

To handle the underflow issue you need to calculate at a larger scale, as mentioned in the other answers. For the parameters given in the OP a scale of 25 to 30 is adequate.

I've re-written your code to do all the arithmetic in bc. Rather than just piping commands into bc via echo, I've written a full bc script as a here document inside a Bash script, since that makes it easy to pass parameters from Bash to bc.

#!/usr/bin/env bash

# Binomial probability calculations using bc
# Written by PM 2Ring 2015.07.30

n=144
p='1/72'
m=16
scale=30

bc << EOF
define ncr(n, r)
{
    auto v,i

    v = 1
    for(i=1; i<=r; i++)
    {
        v *= n--
        v /= i
    }
    return v
}

define binprob(p, n, r)
{
    auto v

    v = ncr(n, r)
    v *= (1 - p) ^ (n - r)
    v *= p ^ r
    return v
}

sc = $scale
scale = sc
outscale = 8

n = $n
p = $p
m = $m

for(i=0; i<=m; i++)
{
    v = binprob(p, n, i)
    scale = outscale
    print i,": ", v/1, "\n"
    scale = sc
}
EOF

output

0: .13345127
1: .27066174
2: .27256781
3: .18171187
4: .09021610
5: .03557818
6: .01160884
7: .00322338
8: .00077747
9: .00016547
10: .00003146
11: .00000539
12: .00000084
13: .00000012
14: .00000001
15: 0
16: 0
2

The Intro

Say you want to calculate what percentage 7 is of 116.
You simply divide 7 by 116 and multiply the result by 100.
It should go something like this:

enter image description here

bc -l <<< '(7/116)*100'
6.03448275862068965500

The Problem

You want it accurate to two decimal places so you add scale=2;, expecting this:

enter image description here But you get some strange results:

bc -l <<< 'scale=2; 100*(7/116)'
6.00

You try increasing the scale value:

bc -l <<< 'scale=3; 100*(7/116)'
6.000

For some reason scale=4; is accurate to 2 places, but has these trailing 0s

bc -l <<< 'scale=4; 100*(7/116)'
6.0300

The Reason

The first part of the calculation is accurate to 2 decimal places and the rest gets chopped off. enter image description here enter image description here


The Solution

The ideal solution would be for bash/sh to support floating point calculations natively. Why don't they already? I have no idea. Seems stupid not to. Until then you can make do with these:

bc

  1. Define a variable.
  2. Set the scale.
  3. Divide by 1.
bc -l <<< "x=(7/116)*100; scale=2; x/1"
6.03

printf

  1. Wrap the bc command with "$(...)" for command substitution.
  2. Pass it to printf.
  3. Use %.2f in the format string (%f says float, .2 says accurate to 2 decimal places (like scale=2)).
printf '%.2f\n' "$(bc -l <<< '(7/116)*100')"
6.03

awk

  1. FYI: awk can do arithmetic too
  2. I think echo breaks the interactive loop somehow. There's probably a better way to do it.
  3. It doesn't give you control over the floating point. I'm sure it can be done though somehow.
echo | awk '{print 100*(7/116)}'
6.03448
1
n=144
prob=$(echo "0.0139" | bc)

echo -e "Enter no.:"
read passedno

k=$passedno
nCk2() {
    num=1
    den=1
    for((i = 1; i <= $2; ++i)); do
        ((num *= $1 + 1 - i)) && ((den *= i))
    done
    echo $((num / den))
}

binomcoef=$(nCk2 $n $k)
binprobab=$(echo "$binomcoef*($prob^$k)*((1-$prob)^($n-$k))" | bc -l)
printf "%0.8f\n" $binprobab 
1

As you know it's because 0.0139^5 = .00000000051888844699 is too small and rounded to 0 with scale=8. How about doing intermediate calculations with greater scale, then round the last value with scale=8?

binprobab=$(echo "scale=20; a=$binomcoef*($prob^$k)*((1-$prob)^($n-$k)); scale=8; a/1" | bc)

Dividing by 1 is commonly used to round a number with scale in bc.

0
-1

Thanks for the explanation @voices. The awk solution with printf:

awk 'BEGIN {printf("%.2f\n",100*7/116)}'
5
  • This answer is not even touching on bc, which was the topic of the question. – Kusalananda Dec 14 '19 at 14:26
  • @Kusalananda: I should use reply to voices, becouse his awk 3 solution is not perfect. I am a newbie :P – gabor.zed Dec 15 '19 at 0:40
  • @jesse_b: it's not > but <, awk wants input, maybe there is a switch to disable it, but it was simplier :P – gabor.zed Dec 15 '19 at 0:45
  • @Kusalananda: I don't have enogh reputation to comment. lol. I just tried to help others. well. fork. – gabor.zed Dec 15 '19 at 0:50
  • 1
    @jesse_b: my bad. it really doesn't. thanks – gabor.zed Dec 15 '19 at 1:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.