Passa al contenuto principale

4. Arithmetic evaluation and expansion

We have seen before $((expression)) where expression is an arithmetic expression. It is related to the compound command ((...)) which is used for arithmetic evaluation (truth tests). Here we will see some more arithmetic operators and expressions.

  1. By default numbers are treated as decimals (base 10). But we can also use octal numbers (base 8), hexadecimal numbers (base 16), etc.

    echo $((99))    # decimal
    echo $((077))    # octal
    echo $((0xff))    # hexadecimal
    echo $((2#11))    # binary
    echo $((7#66))    # base 7
  2. Arithmetic operators:

    echo $((5 + 2))
    echo $((5 - 2))
    echo $((5 * 2))
    echo $((5 ** 2))
    echo $((5 / 2))
    echo $((5 % 2))

    An example:

    vim modulo.sh
    modulo.sh
    #!/bin/bash

    # modulo: demonstrate the modulo operator

    for ((i = 0; i <= 20; i = i + 1)); do
    remainder=$((i % 5))
    if (( remainder == 0 )); then
    printf "<%d> " "$i"
    else
    printf "%d " "$i"
    fi
    done
    printf "\n"
    ./modulo.sh
  3. Assignment:

    foo=
    echo $foo
    if (( foo = 5 )); then echo "It is true."; fi
    echo $foo

    The = sign above makes an assignment, and this assignment is successful. To check for equality we can use ==.

    Other assignment operators are: +=, -=, *=, /=, %=.

    There are also incremental/decremental operators: ++ and --

    foo=1
    echo $((foo++))
    echo $foo
    foo=1
    echo $((++foo))
    echo $foo

    Let's see a modified version of the modulo.sh example:

    vim modulo2.sh
    modulo2.sh
    #!/bin/bash

    # modulo: demonstrate the modulo operator

    for ((i = 0; i <= 20; ++i)); do
    if ((i % 5 == 0)); then
    printf "<%d> " "$i"
    else
    printf "%d " "$i"
    fi
    done
    printf "\n"
    diff -u modulo.sh modulo2.sh
    diff -u modulo.sh modulo2.sh
    --- modulo.sh    2023-06-28 01:48:25.000000000 +0000
    +++ modulo2.sh 2023-06-28 01:48:25.000000000 +0000
    @@ -2,9 +2,8 @@

    # modulo: demonstrate the modulo operator

    -for ((i = 0; i <= 20; i = i + 1)); do
    - remainder=$((i % 5))
    - if (( remainder == 0 )); then
    +for ((i = 0; i <= 20; ++i)); do
    + if ((i % 5 == 0)); then
    printf "<%d> " "$i"
    else
    printf "%d " "$i"
    ./modulo2.sh
  4. There are also some operators that work at the bit level:

    • ~ -- Negate all the bits in a number.
    • << -- Shift all the bits in a number to the left.
    • >> -- Shift all the bits in a number to the right.
    • & -- Perform an AND operation on all the bits in two numbers.
    • | -- Perform an OR operation on all the bits in two numbers.
    • ^ -- Perform an exclusive OR operation on all the bits in two numbers.

    There are also corresponding assignment operators (for example, <<=) for all but bitwise negation.

    Let's see an example that prints the powers of 2:

    for ((i=0;i<8;++i)); do echo $((1<<i)); done
  5. The compound command ((...)) supports also comparison operators: ==, !=, <, <=, >, >=, && (logical AND), || (logical OR).

    It also supports the ternary operator: expr1?expr2:expr3. If expression expr1 evaluates to be non-zero (arithmetic true), then expr2; else expr3.

    Logical expressions follow the rules of arithmetic logic; that is, expressions that evaluate as zero are considered false, while non-zero expressions are considered true. The ((...)) compound command maps the results into the shell’s normal exit codes.

    if ((1)); then echo "true"; else echo "false"; fi
    if ((0)); then echo "true"; else echo "false"; fi

    The ternary operator is like a compact if/then/else statement:

    a=0
    ((a<1?++a:--a))
    echo $a
    ((a<1?++a:--a))
    echo $a
    a=$((a<1?a+1:a-1))
    echo $a
  6. Let's see a more complete example of using arithmetic operators in a script that produces a simple table of numbers.

    vim arith-loop.sh
    arith-loop.sh
    #!/bin/bash

    # arith-loop: script to demonstrate arithmetic operators

    finished=0
    a=0
    printf "a\ta**2\ta**3\n"
    printf "=\t====\t====\n"

    until ((finished)); do
    b=$((a**2))
    c=$((a**3))
    printf "%d\t%d\t%d\n" "$a" "$b" "$c"
    ((a<10?++a:(finished=1)))
    done
    ./arith-loop.sh
  7. For complex arithmetics we can use bc, which is an arbitrary precision calculator.

    bc <<< '2 + 2'
    echo '2 + 2' | bc

    This example script calculates monthly loan payments:

    vim loan-calc.sh
    loan-calc.sh
    #!/bin/bash

    # loan-calc: script to calculate monthly loan payments

    PROGNAME="${0##*/}" # Use parameter expansion to get basename

    usage () {
    cat <<- EOF
    Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
    Where:
    PRINCIPAL is the amount of the loan.
    INTEREST is the APR as a number (7% = 0.07).
    MONTHS is the length of the loan's term.
    EOF
    }

    if (($# != 3)); then
    usage
    exit 1
    fi

    principal=$1
    interest=$2
    months=$3

    bc <<- EOF
    scale = 10
    i = $interest / 12
    p = $principal
    n = $months
    a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
    print a, "\n"
    EOF
    ./loan-calc.sh 135000 0.0775 180

    This example calculates the monthly payment for a $135,000 loan at 7.75 percent APR for 180 months (15 years). Notice the precision of the answer. This is determined by the value given to the special scale variable in the bc script.

    For more details about bc see:

    man bc
    info bc
Loading asciinema cast...