Zsh’s arithmetic expansion (( )) is a surprisingly powerful tool that handles more than just basic integer math.

Let’s see it in action. Imagine you want to calculate the total cost of items with a discount.

items=5
price_per_item=12.50
discount_percentage=15

total_cost=$(zsh -c "echo $(( (items * price_per_item) * (1 - discount_percentage / 100) ))")
echo "Total cost: $total_cost"

Running this gives:

Total cost: 53.125

This demonstrates that (( )) can handle floating-point numbers, which is a common point of confusion.

The core problem (( )) and let solve is moving beyond simple string manipulation in shell scripting to perform actual calculations. Historically, shell scripting was clumsy for math. You’d have to pipe numbers to external commands like bc or expr. Zsh (and Bash) introduced built-in arithmetic expansion to streamline this.

Internally, (( )) evaluates arithmetic expressions. It supports standard C-style operators: +, -, *, /, % (modulo), ** (exponentiation), bitwise operators (&, |, ^, ~, <<, >>), and logical/comparison operators (&&, ||, !, ==, !=, <, >, <=, >=). Crucially, it performs floating-point arithmetic by default when non-integer values are involved. Variables within the (( )) are treated as numerical values, and you don’t need to prefix them with $.

The let command is older and primarily designed for integer arithmetic. While it works, (( )) is generally preferred for its flexibility, especially with floating-point numbers and its cleaner syntax. For instance, let "x = 5 + 3" works, but (( x = 5 + 3 )) is more idiomatic. let also allows assignment and evaluation in one go.

Here’s a breakdown of common operations and how (( )) handles them:

  • Integer Arithmetic:

    a=10
    b=3
    echo $(( a + b )) # Output: 13
    echo $(( a - b )) # Output: 7
    echo $(( a * b )) # Output: 30
    echo $(( a / b )) # Output: 3 (integer division by default if both operands are integers)
    echo $(( a % b )) # Output: 1 (remainder)
    

    The integer division behavior is important: 10 / 3 results in 3, not 3.333....

  • Floating-Point Arithmetic:

    x=10.5
    y=3.2
    echo $(( x * y )) # Output: 33.6
    echo $(( x / y )) # Output: 3.28125
    

    As you see, Zsh automatically promotes to floating-point when decimal values are present. You don’t need to do anything special.

  • Exponentiation:

    echo $(( 2 ** 8 )) # Output: 256
    echo $(( 3 ** 2.5 )) # Output: 15.588457268119896
    

    The ** operator is for exponentiation.

  • Bitwise Operations:

    a=5 # Binary 0101
    b=3 # Binary 0011
    echo $(( a & b )) # Output: 1 (Binary 0001) - Bitwise AND
    echo $(( a | b )) # Output: 7 (Binary 0111) - Bitwise OR
    echo $(( a ^ b )) # Output: 6 (Binary 0110) - Bitwise XOR
    echo $(( ~a ))    # Output: -6 - Bitwise NOT (two's complement)
    echo $(( a << 1 )) # Output: 10 (Binary 1010) - Left Shift
    echo $(( a >> 1 )) # Output: 2 (Binary 0010) - Right Shift
    

    These are less common in general scripting but powerful for low-level tasks or specific algorithms.

  • Logical and Comparison Operators:

    a=10
    b=20
    echo $(( a < b ))  # Output: 1 (True, represented as 1)
    echo $(( a > b ))  # Output: 0 (False, represented as 0)
    echo $(( a == 10 )) # Output: 1 (True)
    echo $(( a != b )) # Output: 1 (True)
    echo $(( a && b )) # Output: 1 (True, as both a and b are non-zero)
    echo $(( a || b )) # Output: 1 (True)
    

    These are useful within conditional statements.

When you use zsh -c "...", you’re telling Zsh to execute the string provided as a command. This is useful for capturing the output of an arithmetic expansion into a variable, as shown in the initial example. If you’re doing calculations within an existing Zsh script, you can directly use (( )):

items=5
price_per_item=12.50
discount_percentage=15

# Calculate total cost directly within the script
total_cost_int=$(( (items * price_per_item) * (1 - discount_percentage / 100) ))

echo "Total cost: $total_cost_int"

A subtle but powerful aspect of (( )) is its ability to perform assignments within the expression itself, effectively acting like let. You can also combine operations.

a=5
(( a += 3 )) # Equivalent to (( a = a + 3 ))
echo $a      # Output: 8

(( a *= 2 )) # Equivalent to (( a = a * 2 ))
echo $a      # Output: 16

This makes (( )) a mini-calculator embedded directly in your shell. It’s not just for simple sums; it’s for complex expressions, floating-point precision, and even bit manipulation, all without leaving the shell environment.

The next common pitfall after mastering basic arithmetic is understanding operator precedence and associativity, especially when constructing complex expressions that mimic mathematical formulas.

Want structured learning?

Take the full Zsh course →