Introduction to Zsh Arithmetic Operations
Zsh (Z shell) is a powerful shell that extends the capabilities of traditional shells like Bash with richer features, better customization, and more intuitive scripting syntax. One area where Zsh truly shines is arithmetic operations. Whether you're writing a quick automation script, building a CLI tool, or performing complex calculations, understanding how Zsh handles arithmetic is essential for any developer working in Unix-like environments.
Unlike some shells that require external tools like bc or expr for even basic math, Zsh provides robust built-in arithmetic evaluation. This guide walks you through everything from basic integer math to floating-point precision, bitwise operations, arrays, and best practices for writing clean, maintainable arithmetic code in Zsh.
Why Arithmetic Operations Matter in Zsh Scripting
Arithmetic operations are the backbone of many shell scripts. You'll encounter them when:
- Iterating through loops with counters and step values
- Calculating file sizes, memory usage, or disk space
- Generating sequences for batch processing
- Implementing conditional logic based on numeric comparisons
- Manipulating exit codes and signal values
- Building interactive menus with numeric input validation
Using Zsh's native arithmetic capabilities instead of external commands offers several advantages: faster execution (no process spawning), cleaner syntax, better integration with shell variables, and access to advanced features like floating-point math without external dependencies.
How to Use Arithmetic in Zsh
1. The Arithmetic Evaluation Syntax
Zsh provides multiple ways to perform arithmetic evaluation. The most common is the double-parenthesis syntax, which evaluates the enclosed expression arithmetically.
#!/usr/bin/env zsh
# Basic arithmetic evaluation with (( ))
(( sum = 5 + 3 ))
echo "Sum is: $sum"
# The result of the last expression is available in $?
(( 10 * 2 ))
echo "Last result: $?"
# You can also use the $(( )) form to embed results in strings
echo "10 + 20 = $(( 10 + 20 ))"
The (( )) form is used as a statement (often for assignments or conditions), while $(( )) is used when you need to capture and use the result as a value.
2. Basic Operators
Zsh supports all standard arithmetic operators. Here's a comprehensive overview:
#!/usr/bin/env zsh
a=15
b=4
echo "Addition: $(( a + b ))" # 19
echo "Subtraction: $(( a - b ))" # 11
echo "Multiplication: $(( a * b ))" # 60
echo "Division: $(( a / b ))" # 3 (integer division)
echo "Modulo: $(( a % b ))" # 3
echo "Exponentiation: $(( a ** 2 ))" # 225
Note that division between integers truncates toward zero by default. To get a fractional result, at least one operand must be a floating-point number (more on this below).
3. Assignment and Compound Operators
Zsh supports shorthand assignment operators that combine an operation with assignment, making your scripts more concise.
#!/usr/bin/env zsh
x=10
(( x += 5 )) # x = x + 5 -> 15
echo "After += : $x"
(( x -= 3 )) # x = x - 3 -> 12
echo "After -= : $x"
(( x *= 2 )) # x = x * 2 -> 24
echo "After *= : $x"
(( x /= 4 )) # x = x / 4 -> 6
echo "After /= : $x"
(( x %= 4 )) # x = x % 4 -> 2
echo "After %= : $x"
(( x **= 3 )) # x = x ** 3 -> 8
echo "After **= : $x"
4. Increment and Decrement
Like C and many other languages, Zsh supports pre- and post-increment/decrement operators.
#!/usr/bin/env zsh
count=5
# Post-increment: returns current value, then increments
echo "Post-increment: $(( count++ ))" # 5
echo "Count is now: $count" # 6
# Pre-increment: increments first, then returns new value
echo "Pre-increment: $(( ++count ))" # 7
echo "Count is now: $count" # 7
# Same applies for decrement
echo "Post-decrement: $(( count-- ))" # 7
echo "Pre-decrement: $(( --count ))" # 5
5. Floating-Point Arithmetic
One of Zsh's standout features is native floating-point support. This is enabled by loading the zsh/mathfunc module, which also provides mathematical functions.
#!/usr/bin/env zsh
# Enable floating-point and math functions
zmodload zsh/mathfunc
# Floating-point division
echo "Float division: $(( 15.0 / 4.0 ))" # 3.75
# Using math functions
echo "Square root of 16: $(( sqrt(16) ))" # 4
echo "Sin of pi/2: $(( sin(3.14159 / 2) ))" # ~1
echo "Log base e of 10: $(( log(10) ))" # ~2.302585
echo "Power: $(( pow(2, 10) ))" # 1024
# Controlling precision with printf
result=$(( 22.0 / 7.0 ))
printf "Pi approximation: %.4f\n" "$result" # 3.1429
Available math functions include sqrt, cbrt, sin, cos, tan, asin, acos, atan, log, log10, exp, pow, floor, ceil, rint, abs, and more.
6. Comparison and Logical Operators
Arithmetic comparisons return 1 for true and 0 for false, which is the opposite of typical shell exit codes. This is important to remember when using them in conditions.
#!/usr/bin/env zsh
x=10
y=20
# Comparison operators
(( x < y )) && echo "$x is less than $y"
(( x > y )) || echo "$x is not greater than $y"
(( x == 10 )) && echo "x equals 10"
(( x != y )) && echo "x is not equal to y"
(( x >= 10 )) && echo "x is at least 10"
(( y <= 20 )) && echo "y is at most 20"
# Logical operators
(( x > 5 && y > 15 )) && echo "Both conditions true"
(( x > 100 || y > 15 )) && echo "At least one condition true"
(( ! (x == y) )) && echo "x is not equal to y"
7. Bitwise Operators
Zsh supports bitwise operations, which are useful for low-level programming, flag manipulation, and working with binary data.
#!/usr/bin/env zsh
a=12 # Binary: 1100
b=10 # Binary: 1010
echo "Bitwise AND: $(( a & b ))" # 8 (1000)
echo "Bitwise OR: $(( a | b ))" # 14 (1110)
echo "Bitwise XOR: $(( a ^ b ))" # 6 (0110)
echo "Bitwise NOT: $(( ~a ))" # -13
echo "Left shift: $(( a << 2 ))" # 48
echo "Right shift: $(( a >> 2 ))" # 3
8. Using Variables and Arrays
Inside arithmetic expressions, you don't need the $ prefix for variable names, though it still works. Arrays can also be accessed directly.
#!/usr/bin/env zsh
# Variables in arithmetic - no $ needed
width=10
height=5
(( area = width * height ))
echo "Area: $area"
# Using $ is also valid
echo "Area again: $(( $width * $height ))"
# Arrays in arithmetic
numbers=(1 2 3 4 5)
echo "First element: $(( numbers[1] ))" # Zsh arrays are 1-indexed
echo "Third element: $(( numbers[3] ))"
# Sum of array elements
total=0
for n in $numbers; do
(( total += n ))
done
echo "Array sum: $total"
# Array length in arithmetic
echo "Array length: $(( #numbers ))"
9. Conditional Expressions with Arithmetic
The (( )) construct is commonly used in if statements and loops because it returns a proper exit status based on the truthiness of the expression.
#!/usr/bin/env zsh
score=85
if (( score >= 90 )); then
echo "Grade: A"
elif (( score >= 80 )); then
echo "Grade: B"
elif (( score >= 70 )); then
echo "Grade: C"
else
echo "Grade: F"
fi
# Using arithmetic in a while loop
counter=1
while (( counter <= 5 )); do
echo "Iteration $counter"
(( counter++ ))
done
# Using arithmetic in a for loop with C-style syntax
for (( i = 0; i < 5; i++ )); do
echo "Index: $i"
done
10. The let Command
Zsh also supports the let builtin, which is an alternative way to perform arithmetic. It's less common in modern scripts but still useful to recognize.
#!/usr/bin/env zsh
let "x = 5 + 3"
let "y = x * 2"
echo "x = $x, y = $y"
# Multiple expressions
let "a = 10" "b = 20" "c = a + b"
echo "a=$a b=$b c=$c"
11. Bases and Number Conversion
Zsh can handle numbers in different bases (binary, octal, hexadecimal) and convert between them easily.
#!/usr/bin/env zsh
# Hexadecimal
echo "Hex 0xFF = $(( 0xFF ))" # 255
echo "Hex 0x1A = $(( 0x1A ))" # 26
# Octal
echo "Octal 017 = $(( 017 ))" # 15
# Binary
echo "Binary 2#1100 = $(( 2#1100 ))" # 12
# Specifying base with # syntax
echo "Base 8 #17 = $(( 8#17 ))" # 15
echo "Base 16 #FF = $(( 16#FF ))" # 255
# Output in different bases using printf
value=255
printf "Decimal: %d\n" "$value"
printf "Hex: %x\n" "$value"
printf "Octal: %o\n" "$value"
12. Practical Example: File Size Calculator
Let's combine what we've learned into a practical script that calculates and formats file sizes.
#!/usr/bin/env zsh
zmodload zsh/mathfunc
format_size() {
local bytes=$1
local units=(B KB MB GB TB)
local index=0
local size=$bytes
while (( size >= 1024 && index < 4 )); do
size=$(( size / 1024.0 ))
(( index++ ))
done
printf "%.2f %s\n" "$size" "${units[$(( index + 1 ))]}"
}
# Get file size in bytes
file_path="$1"
if [[ ! -f "$file_path" ]]; then
echo "Usage: $0 "
exit 1
fi
file_bytes=$(stat -f%z "$file_path" 2>/dev/null || stat -c%s "$file_path" 2>/dev/null)
echo "File: $file_path"
echo "Size: $(format_size "$file_bytes")"
echo "Bytes: $file_bytes"
13. Practical Example: Fibonacci Sequence
#!/usr/bin/env zsh
# Generate Fibonacci numbers up to a limit
fibonacci() {
local n=$1
local a=0
local b=1
local temp
for (( i = 0; i < n; i++ )); do
echo -n "$a "
(( temp = a + b ))
(( a = b ))
(( b = temp ))
done
echo
}
echo "First 10 Fibonacci numbers:"
fibonacci 10
Best Practices
Always Use (( )) for Arithmetic
Avoid using expr or external commands for arithmetic when Zsh's built-in evaluation is available. It's faster, cleaner, and less error-prone.
# Bad - spawns external process
result=$(expr 5 + 3)
# Good - uses native evaluation
(( result = 5 + 3 ))
Load zsh/mathfunc for Advanced Math
If your script uses floating-point math or mathematical functions, load the zsh/mathfunc module at the top of your script. This makes your dependencies explicit.
#!/usr/bin/env zsh
zmodload zsh/mathfunc
# Now you can use sqrt, sin, cos, etc.
radius=5
area=$(( 3.14159 * radius ** 2 ))
printf "Circle area: %.2f\n" "$area"
Use printf for Precision Control
When working with floating-point numbers, use printf instead of echo to control decimal precision and avoid ugly long outputs.
value=$(( 22.0 / 7.0 ))
printf "Result: %.4f\n" "$value" # Clean: 3.1429
Quote Variables When Unsure
While Zsh's arithmetic context is generally safe, be careful when interpolating user input. Validate numeric input before using it in arithmetic expressions.
#!/usr/bin/env zsh
read "input?Enter a number: "
# Validate that input is a number
if [[ "$input" =~ ^[0-9]+$ ]]; then
(( doubled = input * 2 ))
echo "Doubled: $doubled"
else
echo "Error: Please enter a valid integer"
exit 1
fi
Prefer Pre-Increment in Loops
While both pre- and post-increment work, pre-increment is slightly more intuitive in loop contexts and avoids subtle bugs when the return value matters.
Comment Complex Expressions
Arithmetic expressions can become hard to read. Break them down or add comments for clarity.
# Calculate compound interest
# Formula: A = P * (1 + r/n)^(n*t)
principal=1000
rate=0.05
times_compounded=12
years=5
amount=$(( principal * (1 + rate / times_compounded) ** (times_compounded * years) ))
printf "Final amount: %.2f\n" "$amount"
Be Mindful of Integer vs Float Division
This is a common source of bugs. Integer division truncates, while float division preserves the fractional part.
# Integer division - loses precision
echo $(( 7 / 2 )) # 3
# Float division - preserves precision
echo $(( 7.0 / 2 )) # 3.5
echo $(( 7 / 2.0 )) # 3.5
Conclusion
Zsh's arithmetic capabilities are surprisingly powerful, offering everything from basic integer math to floating-point precision, bitwise operations, and mathematical functions through the zsh/mathfunc module. By mastering the (( )) and $(( )) syntaxes, understanding the difference between integer and floating-point division, and following best practices like using printf for precision control and validating user input, you can write robust, efficient shell scripts that handle numeric operations with confidence. Whether you're automating system tasks, processing data, or building CLI tools, Zsh's native arithmetic evaluation gives you the tools you need without relying on external commands, making your scripts faster and more maintainable.