← back to latest

Do decimal math in the shell when $(( )) only gives you integers

echo 'scale=3; 2/3' | bc

anatomy

echo '...'
bc reads its program from standard input, so the expression arrives through a pipe. Single quotes keep the shell away from it entirely: no variable expansion, no globbing of a stray `*`. Switch to double quotes only when you want a shell variable spliced into the expression.
scale=3
A bc variable, not a flag. It sets how many digits survive after the decimal point in a division result. The default is 0, which is why bare `echo '2/3' | bc` prints 0 and looks broken. Extra digits are cut off, never rounded.
;
Statement separator. bc treats it the same as a newline, which is what lets a whole program fit inside one quoted string.
2/3
An expression statement. bc prints the value of any statement that is not an assignment, so this line produces output and `scale=3` does not.
bc
An arbitrary-precision calculator language. Numbers are stored as decimal digit strings rather than binary floats, so 0.1 plus 0.2 is exactly 0.3 and integers never overflow. The cost is that you choose the precision yourself, statement by statement.

Sample output

$ echo $(( 2 / 3 ))
0

$ echo '2/3' | bc
0

$ echo 'scale=3; 2/3' | bc
.666

Two oddities in one line. The answer is truncated rather than rounded, so 0.6666 becomes .666, not .667, and bc drops the leading zero before the decimal point. Both are correct according to the language. Neither looks like what you expected, and the variants below fix each.

When you would reach for it

Shell arithmetic is integer only, so any script that turns bytes into gigabytes, works out a percentage, or averages a column of response times either drops the fraction or needs help. bc is that help, and it is already on the machine. It also handles numbers too large for $(( )), which wraps around silently once a value passes 2^63 - 1.

Gotchas

  • Truncation happens at every division, not once at the end, so order matters. scale=1; 37 / 52 * 100 prints 70.0, while scale=1; 100 * 37 / 52 prints 71.1. The first version cuts 0.7115 down to 0.7 before multiplying. Multiply first, divide last, or set scale a few digits higher than you need and round at the end.
  • scale does not limit multiplication. scale=0; 1.5*3 still prints 4.5, because a product keeps the decimals of its operands. Division, %, ^, and sqrt() respect scale; *, +, and - mostly ignore it. Running bc -l sets scale to 20 and loads the math library, which is why it seems to fix everything at once.
  • ibase changes how every number after it is read, including the next assignment. ibase=16; obase=10; FF prints FF, not 255, because the 10 was read in hex and set the output base to 16. Set obase first, or write obase=A when the input base is already 16.
  • Results wider than 70 characters wrap with a trailing backslash, which breaks anything parsing the output. Set BC_LINE_LENGTH=0 in the environment to turn the wrapping off. GNU bc on Debian and the bc that ships with macOS both honor it. Minimal container images, Alpine and debian:bookworm-slim among them, often leave bc out entirely, so apk add bc or apt install bc before a script depends on it.

Variants

$ printf '%.2f\n' "$(echo '2/3' | bc -l)"

Rounding and the leading zero in one step. bc computes at 20 digits, then printf rounds to two and prints 0.67

$ bytes=$(wc -c < large-file.img); bc <<< "scale=2; $bytes / 1024 / 1024"

Bash and zsh. A here-string replaces the echo, and double quotes let the shell splice a measured value into the expression. Prints 5.00 for a 5 MB file

$ echo 'obase=16; ibase=2; 11111111' | bc

Base conversion in either direction, with the assignments in the safe order. Prints FF

lineage

bc came out of Bell Labs in the mid-1970s, written by Lorinda Cherry and Robert Morris, and it first shipped with Version 6 Unix. It was never a calculator in its own right. It was a compiler: bc translated its C-like syntax into the reverse-Polish language of dc, an even older desk calculator that Morris had written for the PDP-11 before Unix had a C compiler, and piped the result straight into it. That two-stage design lasted until Philip A. Nelson wrote GNU bc around 1991 as a standalone interpreter, and POSIX standardized the language shortly afterward, which is why a 1970s calculator is still sitting in /usr/bin on nearly every Unix you will log into. Apple later replaced the GNU version with Gavin Howard's independent implementation, which follows the same standard, so the scripts kept working and almost nobody noticed.