Calculate with decimal numbers

From MikroTik Wiki
Revision as of 05:26, 27 July 2009 by Dshove (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This is a script to do any math operation (+, -, *, and /) with decimal numbers and/or integers in RouterOS. Make sure to copy the text below EXACTLY and paste into a blank Mikrotik script's source. There are no command dependencies, just scripting syntax.

To use it, it's simply a matter of setting the decimals and decimalop global variables from another script or the console.

3.x version

--Doug 06:26, 27 July 2009 (EEST) Update: internally remove leading zeros when calculating

# Performs operations on decimal numbers
# This script can be run from command line or other script.
# To run, set global variables then run this script
#
# Global variables (input):
# decimalop = math operator ( +, -, *, or / )
# decimalplaces = decimal point places (0 = no decimal point)
# decimals = string of decimal numbers seperated by spaces (non-negative (-))
#
# Global variables (output):
# decimalresult = result of calculation, "err" if error
#
# Example:
#   add 3 numbers (4.32, 28 and 0.12):
#        :global decimalop "+"
#        :global decimals "4.32 28 0.21"
#        /system script run <this script>
#        :put $decimalresut




# ### Math Operations ###
# Do not change this part

:global decimals
:global decimalop
:global decimalplaces
:global decimalresult "err"

:local err 0
:local result ""
:local divprecision "10"
:local decimalsarray ""

# Sanity checks
:if ([:len $decimalplaces] = 0) do={:set decimalplaces 4} else={
  :for cindex from=0 to=([:len $decimalplaces] - 1) do={
    :local char [:pick $decimalplaces $cindex ($cindex + 1)]
    :if ($char != "0" && $char != "1" && $char != "2" && $char != "3" && $char != "4" && \
          $char != "5" && $char != "6" && $char != "7" && $char != "8" && $char != "9") do={
      :set err ($err + 1)
    }
  }
}
:if ($decimalop != "+" && $decimalop != "-" && $decimalop != "*" && $decimalop != "/") do={
  :set err ($err + 1)
}

# Check decimals input and convert decimals to array for easier use
:for d from=0 to=([:len $decimals] - 1) do={
  :local char [:pick $decimals $d ($d + 1)]
  :if ($char = " ") do={
    :set decimalsarray ($decimalsarray . ",")
  } else={
    :if ($char = "." || $char = "0" || $char = "1" || $char = "2" || $char = "3" || $char = "4" || \
       $char = "5" || $char = "6" || $char = "7" || $char = "8" || $char = "9" ) do={
      :set decimalsarray ($decimalsarray . $char)
    } else={:set err ($err + 1)}
  }
}
:set decimalsarray [:toarray $decimalsarray]
:foreach d in=$decimalsarray do={
  :local dps 0
  :for cindex from=0 to=([:len $d] - 1) do={
    :if ([:pick $d $cindex ($cindex + 1)] = ".") do={:set dps ($dps + 1)}
  }
  :if ($dps > 1) do={:set err ($err + 1)}
}

# Only preform calculation if no error
:if ($err = 0) do={

# Preform calculation from first to last number
    :local cloopindex 0
    :local decimallen 0
    :local curdeclen 0
    :local tmpnum ""

#   get decimal length for use after calculation
    :foreach d in=$decimalsarray do={
      :local decfound 0
      :for cindex from=0 to=([:len $d] -  1) do={
        :if ([:pick $d $cindex ($cindex + 1)] = ".") do={
          :set curdeclen ([:len $d] - ($cindex + 1))
          :if ($decimalop = "+") do={:if ($curdeclen > $decimallen) do={:set decimallen $curdeclen}}
          :if ($decimalop = "-") do={:if ($curdeclen > $decimallen) do={:set decimallen $curdeclen}}
          :if ($decimalop = "*") do={:set decimallen ($decimallen + $curdeclen)}
          :set decfound 1
        }
      }
      :if ($decfound = 0) do={
        :if ($decimalop = "+") do={:if ($decimallen < 1) do={:set decimallen 1}}
        :if ($decimalop = "-") do={:if ($decimallen < 1) do={:set decimallen 1}}
        :if ($decimalop = "*") do={:set decimallen ($decimallen + 1)}
      }
    }

#  Loop through decimals from start to finish
    :foreach d in=$decimalsarray do={
      :local decfound 0
      :for cindex from=0 to=([:len $d] -  1) do={
        :if ([:pick $d $cindex ($cindex + 1)] = ".") do={
          :set decfound 1
          :set curdeclen [:len [:pick $d ($cindex + 1) [:len $d]]]
          :set tmpnum ([:pick $d 0 $cindex] . [:pick $d ($cindex + 1) [:len $d]])
        }
      }
      :if ($decfound = 0) do={
        :set curdeclen 1
        :set tmpnum ($d . "0")
      }


#     Strip leading zeros
        :local zeroindex ([:len $tmpnum] - 1)
        :for cindex from=([:len $tmpnum] - 1) to=0 do={
          :if ($cindex >= 0 && [:pick $tmpnum $cindex ($cindex + 1)] != "0") do={
            :set zeroindex $cindex
          }
        }
        :set tmpnum [:pick $tmpnum $zeroindex [:len $tmpnum]]

#   Add and Subtract calculation
      :if ($decimalop = "+" || $decimalop = "-") do={

#     add zeros to decimal
        :if ($curdeclen < $decimallen) do={
          :for cindex from=$curdeclen to=($decimallen - 1) do={
            :set tmpnum ($tmpnum . "0")
          }
        }
        :if ($decimalop = "+") do={
          :if ($decimallen > 0) do={
            :if ($cloopindex = 0) do={:set result $tmpnum} \
              else={:set result ($result + $tmpnum) }
          }
        }
        :if ($decimalop = "-") do={
          :if ($decimallen > 0) do={
            :if ($cloopindex = 0) do={:set result $tmpnum} \
              else={:set result ($result - $tmpnum) }
          }
        }

#   end add and subtract
      }


#   Multiply calculation
      :if ($decimalop = "*") do={
        :if ($decimallen > 0) do={
          :if ($cloopindex = 0) do={:set result $tmpnum} \
            else={:set result ($result * $tmpnum)}
        }

#   end multiply
      }

# Divide calculation
      :if ($decimalop = "/") do={
        :if ($cloopindex = 0) do={:set result $d} else={
#       calculate decimal point position and strip decimal point from result
          :local resultdecpos 0
          :local resultlen 0
          :local tmpresult ""

#       Strip out decimal point
          :for cindex from=0 to=([:len $result] - 1) do={
            :if ([:pick $result $cindex ($cindex + 1)] = ".") do={
              :set resultdecpos $cindex
            } else={:set tmpresult ($tmpresult . [:pick $result $cindex ($cindex + 1)])}
          }
          :set result $tmpresult
          :set resultlen [:len $result]

          :if ($curdeclen > ([:len $result] - $resultdecpos)) do={
            :set resultdecpos 0
#         add zeros to result
            :for r from=([:len $result] - $resultdecpos) to=$curdeclen do={
              :set result ($result . "0")
            }
          } else={
            :set resultdecpos ($resultdecpos + $curdeclen)
          }

#       Strip leading zeros
          :local zeroindex ([:len $result] - 1)
          :for cindex from=([:len $result] - 1) to=0 do={
            :if ($cindex >= 0 && [:pick $result $cindex ($cindex + 1)] != "0") do={
              :set zeroindex $cindex
            }
          }
          :set result [:pick $result $zeroindex [:len $result]]

          :local decimal ""
          :local dividend $result
          :local divisor $tmpnum
          :local quotient ($dividend / $divisor)
          :local remainder ($dividend - ($divisor * $quotient))

          :if ($remainder > 0) do={
            :local tmpremainder ($remainder . "0")
            :for x from=1 to=$divprecision do={
              :local tmpdecimal ($tmpremainder / $divisor)
              :set decimal ($decimal . $tmpdecimal)
              :set tmpremainder (($tmpremainder - ($tmpdecimal * $divisor)) . "0")
            }
          } else={:set decimal "0"}

          :set result ""
#       shift decimal point left
          :if (($resultlen - $resultdecpos) > 0) do={
            :if ([:len $quotient] < ($resultlen - $resultdecpos)) do={
              :for cindex from=[:len $quotient] to=($resultlen - $resultdecpos - 1) do={
                :set result ("0" . $result)
              }
              :set result ("0." . $result . $quotient)
              :set resultdecpos 1
            } else={
              :for cindex from=0 to=([:len $quotient]) do={
                :if ($cindex = ([:len $quotient] - ($resultlen - $resultdecpos))) do={
                  :if ($cindex = 0) do={
                    :set result "0."
                    :set resultdecpos 1
                  } else={
                    :set result ($result . ".")
                    :set resultdecpos $cindex
                  }
                }
                :set result ($result . [:pick $quotient $cindex ($cindex + 1)])
              }
            }
            :set result ($result . $decimal)
#            :set resultdecpos [:len $result]
          } else={:set result ($quotient . "." . $decimal); :set resultdecpos [:len $quotient]}
          :set decimallen ([:len $result] - $resultdecpos - 1)

#        if end of array, strip decimal point
          :if ($cloopindex = ([:len $decimalsarray] - 1)) do={
            :local tmpresult ""
            :for cindex from=0 to=([:len $result] - 1) do={
              :if ([:pick $result $cindex ($cindex + 1)] != ".") do={
                :set tmpresult ($tmpresult . [:pick $result $cindex ($cindex + 1)])
              }
            }
            :set result $tmpresult
          }

#     end cloopindex
        }
#   end divide        
      }

    :set cloopindex ($cloopindex + 1)

# end d for
    }

#  Format result, insert decimal point
    :local newresult ""
    :if (([:len $result] - $decimallen) < 0) do={
      :set newresult "0"
      :for r from=[:len $result] to=($decimallen - 1) do={
        :set newresult ($newresult . "0")
      }
      :set result ($newresult . $result)
    }
    :set newresult ""
    :for r from=0 to=(([:len $result] - $decimallen - 1) + $decimalplaces) do={
      :if ($r = ([:len $result] - $decimallen)) do={
        :if ($r = 0) do={:set newresult ($newresult . "0.")}
        :if ($r > 0) do={
          :if ([:pick $result ($r - 1) $r] = "-") do={:set newresult ($newresult . "0")}
          :set newresult ($newresult . ".")
        }
      }
      :if ($r >= [:len $result]) do={:set newresult ($newresult . "0")} else={
        :set newresult ($newresult . [:pick $result $r ($r + 1)])
      }
    }
    :if ([:len $newresult] > 0) do={:set result $newresult}

#end err check
}

# If no errors occurred, set decimalresult to result of calculation
:if ($err = 0) do={:set decimalresult $result}

# ### End of Math Operations ###