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?

61040 次浏览

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)

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}'

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.

This might work for you:

echo "x=0.1 + 0.1; if(x<1) print 0; x" | bc

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

This one will also handle negative numbers:

echo "0.1 - 0.3" | bc | sed -r 's/^(-?)\./\10./'

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
$ 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.

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.

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:

  1. For everything that only starts with -., replace -. with -0.

  2. For everything that only starts with ., replace . with 0.

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

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