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 / 3results in3, not3.333.... -
Floating-Point Arithmetic:
x=10.5 y=3.2 echo $(( x * y )) # Output: 33.6 echo $(( x / y )) # Output: 3.28125As 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.588457268119896The
**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 ShiftThese 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.