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

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.

Related

floating point math in bash with variables [duplicate]

This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Bash - Calculate the Average of Numbers Inputted
(4 answers)
Closed 1 year ago.
How do you perform floating-point math over variables in bash
the output I get is an integer like number
#! /bin/bash
# finding the average of n numbers
avg=0
read -p "" n
for (( i = 0; i < $n; i++ ))
do
read x
((avg = $avg + $x ))
done
#printf %.3f "$(( avg / n )) "
the goal is to show up to 3 decimal places
3
4
5
6
./avg.sh: line 22: printf: 5 : invalid number
5,000
I tried using | bc but I am missing sth
You can replace the entire loop with awk, which does floating-point math natively:
read -p "" n
awk -v n="$n" '{sum+=$1}; NR==n {printf "%.3f\n", sum/n; exit}'
Explanation: -v n="$n" copies the shell variable n into an awk variable with the same name. {avg+=$1} adds each line of input (well, the first "field" of each line) to a running sum. NR==n {printf "%.3f\n", sum/n; exit} means when it hits the nth line, it prints sum/n to three decimal places and exits.
Found this useful question
Added this section after the for loop
var=$(echo "scale=3; $avg / $n" | bc -l)
echo $var
Here is how you can compute the average of n numbers with POSIX shell arithmetic and have 3 or more decimals:
#!/usr/bin/env sh
decimals=3
# The decimal precision is one digit more than the display decimals,
# so it can have an accurate rounding when formatting.
# The precision factor is 10^(decimals + 1).
precision_factor=$(printf '1%0*d' $((decimals + 1)) 0)
average () {
# Multiply the sum of the arguments by the precision factor,
# and divide by the number of arguments.
# With IFS=+ $* expands to the sum of the arguments $1+$2+$3...
# Example, arguments 5 8 1 7 will expand to 5+8+1+7 because IFS is +
IFS=+ average=$(((($*)*precision_factor)/$#))
# The integer part is the average divided by the precision factor.
integer_part=$((average/precision_factor))
# The decimal part is the average head-stripped from the integer part.
decimal_part=${average#$integer_part}
# Assemble a C-locale (decimal point) string for floating-point average.
float_average="$integer_part.$decimal_part"
# Bash consider floating point arguments to printf must be formatted
# to the current locale. So specify we use the C-locale.
LC_NUMERIC=C printf 'Average: %.*f\n' "$decimals" "$float_average"
}
if [ $# -eq 0 ]; then
printf 'Enter a set of numbers: '
read -r input
average $input
else
average "$#"
fi

How to sum reciprocals in bash? [duplicate]

This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Closed 4 years ago.
I am attempting to find the sum of a list of numbers' reciprocals. To illustrate what I am trying to do, here's a basic example:
With the file:
1
2
3
4
I would be trying to find the sum of 1/1, 1/2, 1/3 and 1/4. Is there a simple bash one-liner to do this? (I am new to bash, so explanations would be welcome!)
You could do something like this:
sed 's|^|1/|' file | paste -sd+ | bc -l
sed 's|^|1/|' prepends 1/ to every line
paste -sd+ joins all lines with a plus sign creating an arithmetic expression 1/1+1/2+1/3+1/4
bc -l evaluates that arithmetic expression and outputs the result
If you're looking for an arithmetical progression, you can use this bash one-liner using the bc command
d=0; for c in {1..4}; do d=`echo "$d + 1/$c" | bc -l`; done; echo "$d"
Its output is 1 + 0.5 + 0.3333 + 0.25 =
2.08333333333333333333
It works by
Setting a variable named d to 0
Creating a for loop that counts from 1 to 4
In the for loop it sets the d variable to the new value $d + 1/$c passed to the bc -l command executing the arithmetic
And outputs the value with an echo command

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