View on GitHub

bash-funk

bash-funk is a collection of useful commands for Bash 3.2 or higher.

Bash-Funk “math” module

The following commands are available when this module is loaded:

  1. -calc
  2. -round
  3. -simple-calc
  4. -test-all-math

License

SPDX-FileCopyrightText: © Vegard IT GmbH (https://vegardit.com)
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

-calc

Usage: -calc [OPTION]... [FORMULA]...

Performs calculations using awk. See https://www.gnu.org/software/gawk/manual/html_node/Arithmetic-Ops.html.

Requirements:
  + Command 'awk' must be available.

Parameters:
  FORMULA
      The formula to calculate.

Options:
    --round PRECISION (integer: 0-?)
        Rounds the value with the given precision.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$ -calc 1.103+1.203
2.306
$ -calc 1.103+1.203 --round 2
2.31
$ -calc 1.109-0.8 --round 2
0.31
$ -calc 1.109-0.8 --round 1
0.3
$ -calc 1.109-0.8 --round 0
0
$ -calc 2^1.2 --round 1
2.3

Implementation:

if [[ ! ${_FORMULA:-} ]]; then
   "-calc: Formula is missing."
   return 1
fi

local formula=""
for part in "${_FORMULA[@]}"; do
   formula="$formula $part"
done

if [[ ${_round:-} ]]; then
   LC_ALL=C awk "BEGIN{printf \"%.${_round}f\n\", ($formula)}"
else
   LC_ALL=C awk "BEGIN{print ($formula)}"
fi

-round

Usage: -round [OPTION]... VALUE PRECISION

Rounds the given decimal value using 'printf' builtin..

Parameters:
  VALUE (required, pattern: "[-+]?[0-9]*\.?[0-9]+")
      The first number.
  PRECISION (required, integer: 0-?)
      Number of decimal digits.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$ -round 1.903 3
1.903
$ -round 1.903 2
1.90
$ -round 1.903 0
2

Implementation:

LC_ALL=C builtin printf "%.*f\n" $_PRECISION $_VALUE

-simple-calc

Usage: -simple-calc [OPTION]... FORMULA

Performs simple floating point operations using awk, perl, python or bc - depending on which command is available.

Parameters:
  FORMULA (required, pattern: "[+-]?[0-9]*\.?[0-9]+[/*^+-][0-9]*\.?[0-9]+")
      The formula in the form of <NUM><OPERATOR><NUM>.

Options:
    --round PRECISION (integer: 0-?)
        Rounds the value with the given precision.
    --using COMMAND (one of: [awk,bc,perl,python])
        Specifies which command to use for calculation.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$ -simple-calc 1.103+1.203
2.306
$ -simple-calc 1.103+1.203 --round 2
2.31
$ -simple-calc 1.109-0.8 --round 2
0.31
$ -simple-calc 1.109-0.8 --round 1
0.3
$ -simple-calc 1.109-0.8 --round 0
0
$ -simple-calc 2^1.2 --round 1
2.3

Implementation:

if [[ $_FORMULA =~ ^([+-]?[0-9]*\.?[0-9]+)([/*^+-])([0-9]*\.?[0-9]+)$ ]]; then
   local leftNumber=${BASH_REMATCH[1]}
   local operator=${BASH_REMATCH[2]}
   local rightNumber=${BASH_REMATCH[3]}
else
   echo "$_fn: Invalid formula."
   return 1
fi

if [[ ! ${_using:-} ]]; then
   if hash awk &>/dev/null; then
      local _using=awk
   elif hash perl &>/dev/null; then
      local _using=perl
   elif hash python &>/dev/null; then
      local _using=python
   elif hash bc &>/dev/null; then
      local _using=bc
   else
      echo "$_fn: No supported command for floating operations available."
      return 1
   fi
fi

case $_using in

   awk)
      if [[ ${_round:-} ]]; then
         LC_ALL=C awk "BEGIN{printf \"%.${_round}f\n\", $leftNumber$operator$rightNumber}"
      else
         LC_ALL=C awk "BEGIN{print $leftNumber$operator$rightNumber}"
      fi
     ;;
   bc)
      if [[ ${_round:-} ]]; then
         # https://stackoverflow.com/questions/16164925/using-fractional-exponent-with-bc
         if [[ $operator == "^" && $rightNumber == *.* ]]; then
            LC_ALL=C builtin printf "%.${_round}f\n" $(bc -l <<< "e($rightNumber*l($leftNumber))")
         else
            LC_ALL=C builtin printf "%.${_round}f\n" $(bc -l <<< "$leftNumber$operator$rightNumber")
         fi
      else
         # https://stackoverflow.com/questions/16164925/using-fractional-exponent-with-bc
         if [[ $operator == "^" && $rightNumber == *.* ]]; then
            LC_ALL=C bc -l <<< "e($rightNumber*l($leftNumber))"
         else
            LC_ALL=C bc -l <<< "$leftNumber$operator$rightNumber"
         fi
      fi
     ;;
   perl)
      if [[ ${_round:-} ]]; then
         if [[ $operator == "^" ]]; then
            LC_ALL=C perl <<< "printf(\"%.${_round}f\", $leftNumber ** $rightNumber)"
         else
            LC_ALL=C perl <<< "printf(\"%.${_round}f\", $leftNumber $operator $rightNumber)"
         fi
      else
         if [[ $operator == "^" ]]; then
            LC_ALL=C perl <<< "print $leftNumber ** $rightNumber"
         else
            LC_ALL=C perl <<< "print $leftNumber $operator $rightNumber"
         fi
      fi
     ;;
   python)
      LC_ALL=C python -c "import math
if '$operator' == '^':
   result=math.pow($leftNumber, $rightNumber)
else:
   result=$leftNumber $operator $rightNumber

if ${_round:--1} > -1:
   result=round(result, ${_round:-0})

result=str(result)

if result.endswith('.0'):
   result=result[:-2]

print(result)
"
    ;;
esac

-test-all-math

Usage: -test-all-math [OPTION]...

Performs a selftest of all functions of this module by executing each function with option '--selftest'.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

-calc --selftest && echo || return 1
-round --selftest && echo || return 1
-simple-calc --selftest && echo || return 1