← Back to DevBytes

Zsh Scripting: Arithmetic Operations Complete Guide

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:

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.

— Ad —

Google AdSense will appear here after approval

← Back to all articles