83

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?

rwos
  • 1,751
  • 1
  • 15
  • 18
  • 6
    After reading all these answers I think it is suprising you accepted as solution an answer which uses the `printf` command. Then, I guess you actually did not want ***to get bc(1) to print the leading zero***. – Jdamian Aug 25 '17 at 07:01
  • 4
    As of this comment all solutions manipulate the output of [tag:bc] in some form or another. So apparently the short answer is _no you can't_ because [tag:bc] itself lacks the proper formatting options. – dtmland Jul 13 '20 at 02:05
  • 1
    @Jdamian he wanted to have the number with a leading zero. If POSIX utils are so convoluted that it can't so be it. Nobody wants "tool X to do Y" they want Y done. – Hejazzman Mar 29 '23 at 06:45
  • @Hejazzman, one of the things StackOverflow trains us all to do is *ask better questions*. I came to this question literally because I Googled the wording of the question because I'm running bc in xargs and don't want external processing. If the OP does indeed mean what you suggest, it wastes time. If you're certain the question is about *padding a number in stdio* rather than *formatting the output of bc*, I'd excourage you to submit that change, or suggest that change to the OP. – ghoti May 11 '23 at 02:49

14 Answers14

51

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}'
Elias Dorneles
  • 22,556
  • 11
  • 85
  • 107
42

This might work for you:

echo "x=0.1 + 0.1; if(x<1) print 0; x" | bc
potong
  • 55,640
  • 6
  • 51
  • 83
  • 5
    Probably worth adding another conditional to check you're not clobbering a negative number, as if you're not careful the following `echo "scale=3; x=-0.5; if(x<1) print 0; x" | bc` would yield 0-.5 . the following `echo "scale=3; x=-0.5; if(x>0 && x<1) print 0; x" | bc` prints `-.5`, which isn't quite right. The following is more robust, but verbose and cumbersome. YMMV. `echo 'scale=3; x=-0.5; if(x>-1 && x<1) { if(x<0) { print "-"; x=0-x }; print 0} ; x' | bc` – Matthew Strasiotto Oct 18 '20 at 16:39
34

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.

Community
  • 1
  • 1
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
  • 1
    Also doesn't work with negative numbers, `echo "0.1 - 0.2" | bc | sed 's/^\./0./'`. If you tag on a `| sed 's/^-\./-0./'` it will, but then it doesn't look all that slick any more, `echo "0.1 - 0.2" | bc | sed 's/^\./0./' | sed 's/^-\./-0./'` – AkselA Apr 19 '17 at 11:02
16

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)
choroba
  • 231,213
  • 25
  • 204
  • 289
  • well, no, actually: `$ printf %f $(bc<<<0.1+0.1) -bash: printf: .2: invalid number`. **Edit:** my bad, locale issue. This works. – rwos Dec 06 '11 at 15:26
  • Huh. What version of bash do you run? – choroba Dec 06 '11 at 15:27
  • `$ printf '%3.1f\n' .02` -> `0.0`. Oops. – unbeli Dec 06 '11 at 15:30
  • 1
    The .02 thing above doesn't matter. The Locale thing does. It makes the whole thing pretty ugly: `LANG=C;printf %1.1f $(bc<<<0.1+0.1)` that's not much nicer than using sed... :) – rwos Dec 06 '11 at 15:33
  • 3
    @rwos I second that. sed's a lot cleaner `echo ".1+.1" | bc | sed 's/^\./0./'` – Shawn Chin Dec 06 '11 at 15:40
  • 1
    @chobra (+1) .... @unbeli. It works fine.. The reason you got `0.0` is because that is what you asked for! `3.1f` means a minimum of 3 output chars inclding the decimal point and sign.. The `.1` means print **only** one digit after the decimal point. ... @rwos. Why do you think you need LANG=C to handle only ASCII digits? ... It certainly isn't an issue in my Linux Ubuntu using the standard UTF-8 locale. – Peter.O Feb 26 '12 at 12:13
  • 2
    `printf "%g\n" $(bc<<<0.1+0.1)` :-) – anishsane Apr 28 '14 at 15:02
5

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.

cafemike
  • 660
  • 5
  • 16
  • With: sed -e 's/\(^\|\s\)-\./-0./' -e 's/\(^\.\|\s\.\)/0./' it would match even inside the line, if there is an empty space sign before the number. Like the output off echo ' "fraction: ";1/3 ' | bc -l – Martin T. Dec 07 '18 at 08:40
4

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.

Community
  • 1
  • 1
3

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
Aquarius Power
  • 3,729
  • 5
  • 32
  • 67
3

This one will also handle negative numbers:

echo "0.1 - 0.3" | bc | sed -r 's/^(-?)\./\10./'
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
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
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • Similar answer to a related question: [How do I use floating-point division in bash?](https://stackoverflow.com/a/28541396/86967) – Brent Bradburn Oct 11 '19 at 21:39
  • 1
    "Doing any non trivial math with `bc` is a fool's errand". Well sir, [I am that fool](https://codegolf.stackexchange.com/a/257615/15940) :) – roblogic Feb 09 '23 at 14:48
3
$ 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.

gomibako
  • 73
  • 1
  • 5
3

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
0

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.

ssanch
  • 389
  • 2
  • 6
0

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.

(edit due to good catch by G.Man...)

r=$(echo "0.1 + 0.1" | bc)             # compute / save result
sign="";                               # set sign to empty str
[[ ${r:0:1} == - ]] && {               # if negative
  sign='-'                             # save - as sign
  r="${r:1}"                           # trim from r
}
[[ $r =~ ^[1-9].*$ ]] || r="0$r"       # test 1st char [1-9] or prepend 0
echo "${sign}$r"                       # output result with sign

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); sign=""; [[ ${r:0:1} == - ]] && { sign='-'; r="${r:1}"; }; [[ $r =~ ^[1-9].*$ ]] || r="0$r"; echo "${sign}$r"
1.4
$ r=$(echo "0.8 - 0.6" | bc); sign=""; [[ ${r:0:1} == - ]] && { sign='-'; r="${r:1}"; }; [[ $r =~ ^[1-9].*$ ]] || r="0$r"; echo "${sign}$r"
0.2

and negative values handled

$ r=$(echo "-0.8 + 0.6" | bc); sign=""; [[ ${r:0:1} == - ]] && { sign='-'; r="${r:1}"; }; [[ $r =~ ^[1-9].*$ ]] || r="0$r"; echo "${sign}$r"
-0.2
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • And, if `r` is `-.4`, this will display ``0-.4`` (rather than ``-0.4``). – G-Man Says 'Reinstate Monica' Apr 10 '23 at 03:06
  • Well, I spend most of my time on U&L (FYI, [How do I get bc to start decimal fractions with a leading zero](https://unix.stackexchange.com/q/292087/80216) links to this question — disclosure: I just posted an answer there), so I didn’t want to spend a lot of time reading the 13 answers here.  And I skimmed over the ones from users who were departed or idle. … … … … … … … … … … … … … … … … … … … … … … … … … Thanks  for  the  acknowledgement. – G-Man Says 'Reinstate Monica' Apr 10 '23 at 08:45
0

Based on cafemike's answer and Martin T.'s comment, i come up with this solution.

f() { printf ' %b' "@" '\n' \
    | bc --mathlib \
    | sed 's/\(^\|[^0123456789]\)\.\([0123456789]\)/\10.\2/g' ; }

f 0 - 1.1
f 0 - 1
f 0 - 0.1
f 0
f 0 + 0.1
f 0 + 1
f 0 + 1.1
f '"pre.fix:";-1/3'
f '"pre.fix: ";-1/3'
f '"pre.fix:";1/3'
f '"pre.fix: ";1/3'

Explanation for the regular expression.

For a dot \. that is at the beginning of the senstence ^ or \| that is not a digit [^0123456789], do the replacement.

\1 is a back reference of the \( round brackets \).

If it is the case of "beginning of the sentence, the back reference is nothing.

If it is the case of "not a digit", the back reference is the one character before the dot. Thus this works for negative sign, or a space, or any symbols or characters (as long as not 0-9).

There is a catch. For any dot within a sentence, it may be replaced with 0. as well. Thus i am adding another back reference \2. It will do the replacement only if the dot is followed by a digit.

midnite
  • 5,157
  • 7
  • 38
  • 52