cannot get complex calculation to work in bc - bash

I use qalculate as my day-to-day calculator and it's great! It is easy enough to type in something like:
(1+10^(-17.2/20)) / (1-10^(-17.2/20))
and get the right answer:
1.320289
But trying to get bc to do this sort of calculation in a bash script is frustrating. Google search spits back many pages demonstrating the simplest kinds of simple math examples using bc, but I have yet to find any pages tackling how to perform more complex calculations. When I type the following at CL:
echo 'scale=50; (1+10^(-17.2/20)) / (1-10^(-17.2/20))' | bc -l
I get the following warning-errors:
Runtime warning (func=(main), adr=25): non-zero scale in exponent
Runtime warning (func=(main), adr=44): non-zero scale in exponent
Runtime error (func=(main), adr=46): Divide by zero
If I try something similar but a little simpler like:
echo '(1-10^(-17.2/20))' | bc -l
I do get an answer, buts it's wrong and comes complete with a warning.
Runtime warning (func=(main), adr=18): non-zero scale in exponent
0
What could bc be having trouble with here, or rather what am I not doing correctly to get bc to perform these calculations properly?

Unfortunately, bc doesn't support exponents such as -17.2/20. If you don't require 50 decimal places of precision, one option would be to use another tool such as awk:
$ awk 'BEGIN{print (1+10^(-17.2/20)) / (1-10^(-17.2/20))}'
1.32029
You can pass variables to awk from your script like this:
$ awk -va="-17.2" -vb="20" 'BEGIN{print (1+10^(a/b)) / (1-10^(a/b))}'
1.32029

from the bc man page:
expr ^ expr:
The result of the expression is the value of the first raised to
the second. The second expression must be an integer.
but since if x = a^b, then ln(x) = ln(a^b) = b(ln(a)), we can see that x = exp(b(ln(a))), so if you want to raise things to fractional b's you can use that.
Note: In bc the actual exp and ln functions are e and l.

Related

Bash - Calculate Percentage [duplicate]

This question already has answers here:
Calculating rounded percentage in Shell Script without using "bc"
(6 answers)
Closed 3 years ago.
I am trying to calculate on a Linux System.
I do have two different numbers, defined with a variable
$1= 1024
$2= 20
My task is now to calculate how many percent are 20 of 1024. The calculation would be (100/1024*20)
The problem is, that bash always says 0 with this type of code:
echo $((100/$1*$2))
Do anyone have an idea how i can calculate this?
you can do this using bc -l command.
Eg. echo "100/1024*20" | bc -l gives 1.953125
Your attempt didn't work because you are performing integer calculation:
100/1024 = 0 // integer calculation
100/1024 = 0.09765625 // floating point calculation
So, you need to explain in some way that floating point calculation is to be done.
You can do it as follows:
awk 'BEGIN {print (100/1024*20)}'
More examples can be found in this post.
You can tell bc to show results in 2 (or desired number of) decimal places.
Use below command
echo "scale=8; 100 / $1 * $2" | bc
On my computer, it reported something like below:
1.95312500
You can change the number of decimal places by passing correct numebr to 'scale' attribute.

Bash operator ++ behavior weired with a constant

By occasionally, I meet a problem when calculating a self-increment operation ++ with a constant, the behavior is weird for me (this is not the original code, I just copied the looks-like lines).
#!/bin/bash
echo "out1="$((++5))
echo "out2="$((5++))
The code snippet looks like about; when I execute it, I got following result:
$ bash test.sh
out1=5
test.sh: line 3: 5++: syntax error: operand expected (error token is "+")
The bash version is:
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
I want to know:
why there is no syntax error for pre-increment operator ?
why the post-increment operate return a value 5, other than 6.
Thanks.
I can't say for sure, I don't feel like browsing the source to learn about the arithmetic expression parser. My guess is:
pre-increment is not actually preincrement, but that you can put any number of + and - symbols before a constant to determine if it's positive or negative. I suspect the parser is seeing $(( +(+5) ))
summary: you're just specifying the number's sign.
as post-incrementing a constant makes no sense (you can't assign the constant 5 with the value 6), it is taken as if you were typing "5 plus ... something" and instead of an arithmetic value you gave another plus sign. So, syntax error, and "operand error" message. Like the parser is seeing $(( (5) + (+) ))
summary: the first plus is OK, the second is an error.

Trying to get an average using the contents of two files

So I have two files in my directory that contain a number in each of them. I want to make a script that calculates the average of these two numbers. How would I write it? Would this be correct?
avg=$((${<file1.txt}-${<file2.txt})/2)
Your example does not work. Furthermore, your formula is probably incorrect. Here are two options without unnecessary cat:
avg=$(( (`<file1.txt` + `<file2.txt`) / 2 ))
or
avg=$(( ($(<file1.txt) + $(<file2.txt)) / 2 ))
I find the first one more readable though. Also be warned: this trivial approach will cause problems when your files contain more than just the plain numbers.
EDIT:
I should have noted that the first syntactical/legacy option which uses the backticks (` `) is no longer recommended and should be avoided. You can read more about the WHY here. Thanks at mklement0 for the link!
EDIT2:
According to Eric, the values are floating point numbers. You can't do this directly in bash because only integer numbers are supported. You have to use a little helper:
avg=$(bc <<< "( $(<file1.txt) + $(<file2.txt) ) / 2")
or maybe easier to understand
avg=$(echo "( $(<file1.txt) + $(<file2.txt) ) / 2" | bc)
For those who might wonder what bc is (see man bc):
bc is a language that supports arbitrary precision numbers with
interactive execution of statements.
Here is another alternative since perl is usually installed by default:
avg=$(perl -e 'print( ($ARGV[0] + $ARGV[1]) / 2 )' -- $(<file1.txt) $(<file2.txt))
You'll want to use a command substitution:
avg=$(($(cat file1.txt)-$(cat file2.txt)/2))
However, Bash is a pretty bad language for doing maths (at least unless it's completely integer maths). You might want to look into bc or a "real" language like Python.

How to count number of lines in file and then use maths on that variable in bash?

I am currently using a bash script to pipe a few other codes together but am new and have been stuck on this for the last day or so. Basically I need to count the number of lines within a file and then divide that by 4 to get the true number of objects in that file (each object takes up 4 lines).
For this I have looked around and ended up with the following code:
a=$(wc -l "${o}"*)
k=$(wc -l Unmatched_forward.fq)
x=4
#declare -i $a
declare -i $k
stats1_2=$((a / x))
stats2_2=$((k / x))
echo "${stats1_2} reads were joined."
echo "${stats2_2} reads were not joined."
Within this code ${o} is the output from a previous file however needs to have ".fq" added to the end but whenever I try to add that to the end it comes up the error message below I have been trying to use the "*" to run on the file of which there are no other files similar.
"Unmatched_forward.fq" is another output file which I want to count the number of objects in.
I am using the declare option because I read that otherwise the number will be in string form instead of an integer and so maths cannot be done.
If anyone can help and explain whats wrong that would be great.
The error message is:
Overlay_code.sh: line 638: declare: `1265272': not a valid identifier
Overlay_code.sh: line 638: declare: `Unmatched_forward.fq': not a valid identifier
Overlay_code.sh: line 643: 1265272 Unmatched_forward.fq: syntax error: invalid arithmetic operator (error token is ".fq")
Whats more confusing is I am suddenly getting the '1265272' number appearing and have no idea why!
You should check that your invocation of wc truly returns only an integer, because I think it is not. Probably the following happens
$> wc -l Unmatched_forward.fq
128 Unmatched_forward.fq
So it returns the line count and the filename.
The following should work
k=$(wc -l Unmatched_forward.fq | awk '{print $1}')
x=4
stats1_2=$((k / x))
Note that bash's (()) only supports integer math, so all results will get rounded. If you need floating point precision, check out bc
You mean declare -i k. When you include the $, you're causing the variable name to be replaced with its value. But you want to say that the variable k is integer-typed.

Evaluating a mathematical expression stored as a string, into a single number (bash)

I am working on Mac OSX and using bash as my shell. I currently have a string which I wish want evaluated as a number. When I echo the string I get 1.e8*1.07**100. Is there any way to pass this string on to be evaluated as a number?
The background as to why it is a string to start with is because the expression was built step by step. First 1.e8*1.07**%%d is within the code, then the user inputs an integer to be taken as what 1.07 will be raised to the power of. So in the example above, the user would have input 100, and thus the script is stuck with 1.e8*1.07**100, which is the correct expression I was hoping for, but I would have liked it to be evaluated when I echo the variable where it is store.
Actual important bits of code:
BASE=$(printf '1.e8*1.07**%%d')
#Get user input assigned to pow
NUM=$(printf ${BASE} ${pow})
echo $NUM #1.e8*1.07**100
Thanks for any help you can offer.
[Edit: I would also like to not just echo the answer, but store it as a variable.]
How about:
python -c "print $NUM"
By the way, you could just write
BASE="1.e8*1.07**%d"
(In fact, you don't even need the quotes.)
In most unix* systems you'll find a tool called bc that can perform calculations. You'll might need to rewrite your input though, I thinks it accepts ^ instead of **, and I'm not sure about the 1.e8 notation.
It happens that perl can evaluate that exact expression
$ x="1.e8*1.07**100"
$ y=$(perl -E "say $x")
$ echo $y
86771632556.6417

Resources