Bash Arithmetic: Integer and Floating-Point Math

Bash treats everything as a string by default, which catches many people out the first time they write total=$count+1 and end up with the literal text 5+1 instead of 6. The shell has dedicated arithmetic forms that evaluate numeric expressions properly, but they only handle integers; for floating-point math you reach for bc or awk. Once you know which form to use where, the math is short and predictable.
This guide explains how Bash arithmetic works, the differences between (( )), $(( )), and let, and how to combine them with external tools when you need decimals.
Why Plain Assignment Does Not Add
A first attempt at incrementing a variable usually looks like this:
count=5
total=$count+1
echo "$total"5+1The shell sees $count as the string 5, joins it with +1, and assigns the result. To force numeric evaluation, use one of the arithmetic forms below.
The (( )) Compound Command
Wrap an expression in double parentheses to evaluate it as integer math:
count=5
(( count = count + 1 ))
echo "$count"6Inside (( )), variable names work without $, the C-style operators are available (+, -, *, /, %, **, ++, --, <<, >>, &, |, ^, &&, ||), and the exit status of the whole expression is non-zero when the result is 0, which means you can use arithmetic in if:
if (( count > 5 )); then
echo "count is above 5"
ficount is above 5The shortcut increment and decrement operators read cleanly in loops:
for (( i = 0; i < 3; i++ )); do
echo "i=$i"
donei=0
i=1
i=2(( )) is the right form when you assign back to a variable or use the result for control flow. It does not print anything to standard output.
The $(( )) Expansion
When you want the value of an expression rather than the side effect, use the $(( )) arithmetic expansion. The result is substituted into the surrounding command like any other expansion:
count=5
echo "Next: $(( count + 1 ))"Next: 6$(( )) is the right form for inline math inside echo, assignments to other variables, command arguments, and printf:
files=42
echo "Average: $(( files / 7 )) per day"Average: 6 per dayNotice that the division truncates: 42 / 7 happens to be exact, but 43 / 7 would yield 6, not 6.14. Bash arithmetic is integer-only; the decimal part is silently discarded.
The let Builtin
let is the older builtin for arithmetic. It evaluates each argument as an expression:
let "count = 5 + 1"
echo "$count"6let is mostly historical at this point. (( )) does the same work without the quoting trap (let count=* would expand the * as a glob unless quoted) and reads more cleanly. New scripts should prefer (( )); let is worth recognizing in code you inherit.
Operators You Will Actually Use
Bash supports a wide list of arithmetic operators. These are the ones that come up in everyday scripts:
+,-,*,/,%- Standard arithmetic and remainder.**- Exponentiation.++,--- Increment and decrement, prefix or postfix.+=,-=,*=,/=,%=- Compound assignment.==,!=,<,<=,>,>=- Numeric comparison (inside(( ))).&&,||,!- Logical AND, OR, NOT.<<,>>,&,|,^,~- Bitwise shifts and operations.
A small example that uses several:
size=1024
if (( size > 0 && size % 2 == 0 )); then
echo "Power of two? $(( (size & (size - 1)) == 0 ? 1 : 0 ))"
fiPower of two? 1The ternary operator (? :) works inside arithmetic contexts, which keeps short branches readable without nesting if.
Number Bases
Bash arithmetic supports several integer bases. Prefix a number to declare its base:
0xNNfor hexadecimal.0NNfor octal.BASE#NNNfor any base from 2 to 64.
Examples:
echo "$(( 0xff ))"
echo "$(( 0755 ))"
echo "$(( 2#1010 ))"255
493
10Watch out for leading zeros: 08 is interpreted as octal and rejected because 8 is not a valid octal digit. If user input may include leading zeros (HTTP status codes parsed from a string, timestamps), strip them before arithmetic with ${var#0} or use the explicit 10# base prefix:
value="08"
echo "$(( 10#$value ))"8Floating-Point Math with bc
Bash itself cannot do decimal math, but it is happy to call out to a tool that can. bc is the arbitrary-precision calculator that ships with most distributions:
echo "scale=2; 43 / 7" | bc6.14The scale=2 directive sets two decimal places of precision. Without it, bc truncates the division the same way Bash does. Use a higher scale for currency or scientific work:
echo "scale=10; 4*a(1)" | bc -l3.1415926532The -l flag loads the math library, which gives you s (sine), c (cosine), a (arctangent), l (natural log), e (exponential), and sqrt. The expression above computes pi from 4 * arctan(1).
To capture the result into a variable:
pi=$(echo "scale=4; 4*a(1)" | bc -l)
echo "pi = $pi"pi = 3.1415bc is the right tool when you need genuine decimals and do not want a heavyweight dependency. For one-off interactive calculations, run bc -l and type expressions at its prompt.
Floating-Point Math with awk
awk is the other obvious choice and is often faster because it does not start a separate calculator process. Use awk for inline expressions inside scripts:
rate=$(awk 'BEGIN { printf "%.2f\n", 43 / 7 }')
echo "rate = $rate"rate = 6.14printf inside awk works the same way as the C function, with %.2f controlling the number of decimal places. For a percentage:
pct=$(awk -v a=23 -v b=89 'BEGIN { printf "%.1f%%\n", (a / b) * 100 }')
echo "pct = $pct"pct = 25.8%Pass values into awk with -v name=value rather than building the expression by string concatenation; the -v form avoids quoting headaches and shell-injection issues when the inputs come from user data.
Increment Counters in a Loop
Counter loops are the bread and butter of arithmetic in scripts, and they lean on the increment and decrement operators . Two equivalent forms work; pick the one that reads best:
count=0
while IFS= read -r line; do
(( count++ ))
done < input.txt
echo "Lines: $count"count=0
while IFS= read -r line; do
count=$(( count + 1 ))
done < input.txt
echo "Lines: $count"The (( count++ )) form is shorter and is what most authors prefer. Both produce the same result and run at the same speed.
If the script uses set -e, avoid (( count++ )) when count may start at zero. The arithmetic command returns a failure status when the expression evaluates to 0, so the first increment can stop the script. Use pre-increment or assignment instead:
(( ++count ))
(( count += 1 ))Troubleshooting
syntax error in expression
A variable that should hold a number contains non-numeric characters (often a leading or trailing space from read, or a stray letter). Inside (( )) and $(( )), Bash reports a syntax error on the offending token. Trim the input with ${var//[[:space:]]/} or validate it before the arithmetic line.
division by 0
A divisor evaluated to zero, often because the variable was empty and Bash treated it as 0. Quote the expansion in your check (if [ -n "$divisor" ]) before the divide, and either skip the operation or substitute a default with ${divisor:-1}.
Result is 0 when it should be a fraction
Bash arithmetic is integer-only. Switch to bc with an explicit scale= or to awk with %.Nf formatting for decimal output.
FAQ
When should I use (( )) versus $(( ))?
Use (( )) for assignments and control flow (if, while, for). Use $(( )) when you want the value substituted into a surrounding command. They share the same expression syntax.
Can I do floating-point math without an external tool?
Not in Bash. ksh93 and zsh support floating-point in their arithmetic contexts; Bash does not. For Bash, route decimals through bc or awk.
Is there a performance difference between bc and awk?awk is usually faster because it runs entirely inside a single process and does not communicate over a pipe. For one-off calculations the difference is negligible; in a tight loop, prefer awk or precompute the values with a single awk call instead of running bc per iteration.
Conclusion
Reach for (( )) and $(( )) whenever a script needs counters, conditions, or quick integer math, and switch to bc or awk the moment decimals enter the picture. Pair that habit with Bash strict mode
and the patterns in our Bash best practices
guide so numeric code keeps working even when the inputs do not.
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page