0

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

bunny
  • 13
  • 4
  • https://stackoverflow.com/questions/12722095/how-do-i-use-floating-point-division-in-bash ? – KamilCuk Jun 27 '21 at 20:42
  • Try giving bc -l a string. E.g. echo “34.6 * 23.3” | bc -l. Also works with variables. – Andre Wildberg Jun 27 '21 at 20:42
  • @AndreWildberg using `-l` does not show decimal places – bunny Jun 27 '21 at 20:50
  • 1
    Also see https://mywiki.wooledge.org/BashFAQ/022 – Shawn Jun 27 '21 at 20:53
  • @bunny Please try the example `echo "34.6 * 23.3" | bc -l` as given to confirm that what you're seeing is a problem with `bc -l` and not with how you applied it to your code – that other guy Jun 27 '21 at 20:56
  • @AndreWildberg this does not help – bunny Jun 27 '21 at 21:17
  • `printf: 5 : invalid number` is an irrelevant error and that command is commented out anyway. Please fix it, e.g. `printf '%.3f' "$(( avg / n ))"`. See [mre] for reference. – wjandrea Jun 27 '21 at 21:24
  • 3
    Does this answer your question? [How do I use floating-point division in bash?](https://stackoverflow.com/questions/12722095/how-do-i-use-floating-point-division-in-bash) – Czaporka Jun 27 '21 at 22:05
  • 1
    @bunny: It's not help mentioning that you _used `bc`_ in a certain way, if the code you posted, does not show any `bc`. BTW, while you certainly can employ `bc` for your task, wouldn't it be easier to write **everything** in a language which can actually do floats? Examples are awk, zsh, Perl or Ruby. – user1934428 Jun 28 '21 at 09:11

3 Answers3

1

Found this useful question

Added this section after the for loop



var=$(echo "scale=3;  $avg / $n" | bc -l)
echo $var 
bunny
  • 13
  • 4
0

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.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
0

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
Léa Gris
  • 17,497
  • 4
  • 32
  • 41