Fang
Fang

Reputation: 2419

printf ignoring format in bash

This is a hackerrank problem:

Compute the average

As a brief summary, you need to sum up a predefined number of integers, then compute the average and print it with 3 digits precision.

I came up with the following code:

read number_of_ints;
sum=0

for number in $(seq 1 $number_of_ints); do 
    read number
    sum=$(($sum+$number))
done 

printf "%.3f\n" | bc -l <<< $sum/$number_of_ints
printf "%.3f\n" $(echo "$sum/$number_of_ints" | bc -l)

However, printf "%.3f\n" | bc -l <<< $sum/$number_of_ints blatantly ignores my format string and simply outputs it with 20 digit precision. Meanwhile printf "%.3f\n" $(echo "$sum/$number_of_ints" | bc -l) does exactly what I want.

I get that the 20 precision digits are rooted in the fact that bc -l preloads a math library, but shouldn't printf "%.3f" still cut this down to three digits?

Upvotes: 0

Views: 829

Answers (3)

M. Nejat Aydin
M. Nejat Aydin

Reputation: 10123

A solution in pure bash, without resorting to bc

#!/bin/bash

sum=0
read n
for ((i = 0; i < n; ++i)); do read m; ((sum += m)); done

sign=1
if ((sum < 0)); then sign=-1; ((sum = -sum)); fi
m=$(( (sum * 10000 / n + 5) / 10 ))
printf '%d.%03d\n' $((m / 1000 * sign)) $((m % 1000))

Upvotes: 0

Paul Hodges
Paul Hodges

Reputation: 15273

The problem is that you aren't using printf correctly. printf expects a format string followed by as many strings as it gets, and it tries to parse them sanely according to the format. You have provided a format, but NO inputs, so it is likely just printing the requested newline to the pipe you set up behind it.

The bc now has two streams defined on stdin, and is ignoring the pipe in favor of the here-string.

printf "%.3f\n" | bc -l <<< $sum/$number_of_ints # two inputs

c.f. this post for a similar issue.

...But you don't need printf at all. See https://linux.die.net/man/1/bc

bc -l <<< "scale=3;$sum/$number_of_ints"

You could, in fact, code the whole thing in bc.

#!/usr/bin/bc
print "\nHow many numbers shall we average? "
scale=3
cnt=read()
c=cnt
while ( c ) {
  c=c-1
  print "\nEnter a number: "
  n=read()
  t=t+n
}
print "\nTotal: ", t, "\n"
print "Average: ", t/cnt, "\n"
quit

Then run it.

$: ./bcavg
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.

How many numbers shall we average? 3

Enter a number: 2

Enter a number: 7

Enter a number: 9

Total: 18
Average: 6.000

Upvotes: 3

Charles Duffy
Charles Duffy

Reputation: 295363

The code as given doesn't actually convey the output from bc to the command line of printf.

One way to span that gap would be with xargs:

bc -l <<<"$sum/$number_of_ints" | xargs printf '%.3f\n'

Upvotes: 2

Related Questions