skip to content

In bash, what does `echo $(( 10 / 3 ))` print, and how do you compute a percentage or an average with a decimal point inside a bash script?

level: juniorimportance: must knowfreq 58%

answer

  1. no decimal point exists here
  2. truncates, and toward zero
  3. multiply before you divide
  4. delegate to awk or bc
  5. printf formats but cannot divide

basics

~20 s

It prints 3. Bash arithmetic is integer-only, so division truncates toward zero and there is no fractional result. For decimals you call out to another tool — awk or bc — or scale the integers up before dividing and format the result with printf.

solid answer

~50 s

Bash has no floating-point arithmetic at all; every value in `$(( ))` is an integer, so `10 / 3` truncates to 3 and `1 / 2` is 0. Truncation is toward zero, so `$(( -7 / 2 ))` is -3 rather than -4, and `%` follows to match. If you actually need decimals, you delegate: `awk 'BEGIN { printf "%.2f\n", 10/3 }'` is the most portable choice since awk is everywhere, and `echo "scale=2; 10/3" | bc` works when bc is installed. A third option keeps everything in bash: scale up first, as in `$(( part * 100 / total ))` for an integer percentage, and add half the divisor before dividing if you want rounding instead of truncation. Note that bash's `printf` can *format* a float you were given, but it cannot compute one.

code

bash · 6 lines
bash
#!/usr/bin/env bash
passed=7 total=9
echo "integer:  $(( 100 * passed / total ))%"          # 77
echo "rounded:  $(( (100 * passed + total / 2) / total ))%"  # 78
awk -v p="$passed" -v t="$total" 'BEGIN { printf "awk:      %.2f%%\n", 100 * p / t }'
printf '%s\n' "scale=2; 100 * $passed / $total" | bc

go deeper

for a junior

Say clearly that bash does integer math only, so $(( 10 / 3 )) is 3, and name awk or bc as the way to get a decimal. Know that a literal like 1.5 is a syntax error inside $(( )).

for a middle

Explain truncation toward zero, the sign of %, and the multiply-before-divide rule for percentages. Be ready to show the scaled-integer trick that keeps a script dependency-free.

for a senior

Reason about the environment: bc is often missing from slim images, awk is not, and silent 64-bit overflow can corrupt byte-count math. Show how you would pass values into awk safely with -v.

for a principal

Frame the boundary decision — at what point numeric requirements, rounding rules or precision mean the task should leave the shell entirely rather than accumulate fixed-point workarounds in a script others must maintain.

## Bash arithmetic is integers, full stop Every value the shell's arithmetic evaluator handles is a signed integer, in practice 64-bit. There is no float type, no automatic promotion, and no error when precision is lost — the fractional part is simply dropped. ```bash echo $(( 10 / 3 )) # 3 echo $(( 1 / 2 )) # 0 echo $(( 7 / 8 )) # 0 ``` Writing a literal with a decimal point is not a workaround; it is a syntax error in the arithmetic evaluator: ```bash echo $(( 1.5 + 1 )) # bash: 1.5 + 1: syntax error: invalid arithmetic operator ``` This is a bash-specific limitation rather than a shell-wide one — ksh93 and zsh do support floating-point arithmetic — but a script written for bash cannot rely on that. ## Truncation, not rounding, and it goes toward zero Division truncates rather than rounding: `$(( 9 / 10 ))` is 0, not 1. For negative operands bash follows C99 and truncates **toward zero**, which is worth stating precisely because floor division would give a different answer: ```bash echo $(( -7 / 2 )) # -3 (toward zero, not -4) echo $(( -7 % 2 )) # -1 (sign follows the dividend) ``` The remainder operator is defined so that `(a / b) * b + (a % b) == a`, which is why `%` can return a negative number — a real surprise if you were using it to index into an array or bucket a hash. Overflow is equally silent. Exceed the 64-bit range and the value wraps around with no diagnostic, so a script multiplying byte counts can produce a confidently wrong negative number. ## Three ways to get a decimal **awk** — the most portable, since awk is mandated by POSIX and present on essentially every system including minimal containers: ```bash awk -v p="$passed" -v t="$total" 'BEGIN { printf "%.1f%%\n", 100 * p / t }' ``` Passing values with `-v` rather than interpolating them into the program text also keeps script variables out of awk's source, which matters when the values are not fully trusted. **bc** — a calculator built for this, but note that it is *not* installed by default on many slim images, and that `scale` must be set explicitly or it also truncates: ```bash echo "scale=2; 100 * $passed / $total" | bc printf '%s\n' "scale=4; 22/7" | bc ``` Without `scale=2`, `echo "10/3" | bc` prints 3 — the same truncation you were trying to escape. `bc -l` loads the math library and defaults scale to 20. **Scaled integers inside bash** — often the best answer when you only need one or two decimal places, because it adds no dependency: ```bash pct=$(( 100 * passed / total )) # whole-number percent, truncated rounded=$(( (100 * passed + total / 2) / total )) # add half the divisor to round tenths=$(( 1000 * passed / total )) # then insert the point when printing printf '%d.%01d%%\n' $(( tenths / 10 )) $(( tenths % 10 )) ``` Multiply **before** you divide — `$(( 100 * passed / total ))` is right, `$(( 100 * (passed / total) ))` is always 0 or 100 because the inner division truncates first. That ordering mistake is the single most common bug in shell percentage math. ## printf formats, it does not compute Bash's `printf` builtin understands `%f` and friends, so `printf '%.2f\n' 3.14159` prints 3.14. It is a formatter: it cannot divide. `printf '%.2f\n' "$(( 10 / 3 ))"` prints 3.00, because the truncation already happened in the arithmetic before printf ever saw the value. `printf` is also how you zero-pad an integer (`printf -v month '%02d' "$m"`) and how you store the result in a variable with `-v` instead of forking a command substitution. ## When to stop If a script is doing enough numeric work that you are threading values through awk repeatedly, that is a signal the job has outgrown the shell. Bash is glue for processes; a task whose core is arithmetic — statistics, currency, anything where rounding rules are part of the requirement — is cheaper and safer in awk itself, Python, or a real program. Reaching for that judgment early is a better answer than an elaborate fixed-point implementation in shell.

  • What does `$(( -7 / 2 ))` evaluate to in bash, and why is that worth knowing?
    It is -3, not -4: bash follows C and truncates toward zero rather than flooring. The remainder matches, so `$(( -7 % 2 ))` is -1. It matters whenever you use `%` to bucket or index, because a negative remainder can produce an out-of-range index from what looks like safe modulo arithmetic.
  • Why is `$(( 100 * (passed / total) ))` almost always wrong?
    The inner division runs first in integer arithmetic, so unless passed equals or exceeds total it truncates to 0 and the whole expression is 0. Multiplying before dividing — `$(( 100 * passed / total ))` — keeps the precision you need. It is the classic ordering bug in shell percentage math.
  • Between awk and bc for decimal math in a script, which would you default to and why?
    awk, because POSIX mandates it and it is present on effectively every system including slim container images, while bc is frequently absent. awk also takes values through `-v` instead of string-interpolated program text, and it formats in the same step with printf. bc is fine when you know it is installed and want `scale` semantics.
  • Can bash's printf be used to divide two numbers?
    No. `printf` only formats values it is given, so `printf '%.2f\n' "$(( 10 / 3 ))"` prints 3.00 — the truncation already happened in the arithmetic. printf is still useful for zero-padding integers and, with `-v`, for assigning a formatted string without a subshell.

saying these in an interview costs you the question

  • Expecting $(( 10 / 3 )) to print 3.33
  • Thinking printf can perform the division
  • Assuming bc is installed everywhere
  • Dividing before multiplying in a percentage
  • Believing division rounds instead of truncating

context