text: var utils = require('./utils/common');
  var error = require('./utils/error');
  var statistical = require('./statistical');
  var information = require('./information');
  var evalExpression = require('./utils/criteria-eval');
  
  exports.ABS = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.abs(number);
  
    return result;
  };
  
  exports.ACOS = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.acos(number);
  
    if (isNaN(result)) {
      result = error.num;
    }
  
    return result;
  };
  
  exports.ACOSH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.log(number + Math.sqrt(number * number - 1));
  
    if (isNaN(result)) {
      result = error.num;
    }
  
    return result;
  };
  
  exports.ACOT = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.atan(1 / number);
  
    return result;
  };
  
  exports.ACOTH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = 0.5 * Math.log((number + 1) / (number - 1));
  
    if (isNaN(result)) {
      result = error.num;
    }
  
    return result;
  };
  
  //TODO: use options
  exports.AGGREGATE = function(function_num, options, ref1, ref2) {
    function_num = utils.parseNumber(function_num);
    options = utils.parseNumber(function_num);
    if (utils.anyIsError(function_num, options)) {
      return error.value;
    }
    switch (function_num) {
      case 1:
        return statistical.AVERAGE(ref1);
      case 2:
        return statistical.COUNT(ref1);
      case 3:
        return statistical.COUNTA(ref1);
      case 4:
        return statistical.MAX(ref1);
      case 5:
        return statistical.MIN(ref1);
      case 6:
        return exports.PRODUCT(ref1);
      case 7:
        return statistical.STDEV.S(ref1);
      case 8:
        return statistical.STDEV.P(ref1);
      case 9:
        return exports.SUM(ref1);
      case 10:
        return statistical.VAR.S(ref1);
      case 11:
        return statistical.VAR.P(ref1);
      case 12:
        return statistical.MEDIAN(ref1);
      case 13:
        return statistical.MODE.SNGL(ref1);
      case 14:
        return statistical.LARGE(ref1, ref2);
      case 15:
        return statistical.SMALL(ref1, ref2);
      case 16:
        return statistical.PERCENTILE.INC(ref1, ref2);
      case 17:
        return statistical.QUARTILE.INC(ref1, ref2);
      case 18:
        return statistical.PERCENTILE.EXC(ref1, ref2);
      case 19:
        return statistical.QUARTILE.EXC(ref1, ref2);
    }
  };
  
  exports.ARABIC = function(text) {
    // Credits: Rafa? Kukawski
    if (!/^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/.test(text)) {
      return error.value;
    }
    var r = 0;
    text.replace(/[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, function(i) {
      r += {
        M: 1000,
        CM: 900,
        D: 500,
        CD: 400,
        C: 100,
        XC: 90,
        L: 50,
        XL: 40,
        X: 10,
        IX: 9,
        V: 5,
        IV: 4,
        I: 1
      }[i];
    });
    return r;
  };
  
  exports.ASIN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.asin(number);
  
    if (isNaN(result)) {
      result = error.num;
    }
  
    return result;
  };
  
  exports.ASINH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.log(number + Math.sqrt(number * number + 1));
  };
  
  exports.ATAN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.atan(number);
  };
  
  exports.ATAN2 = function(number_x, number_y) {
    number_x = utils.parseNumber(number_x);
    number_y = utils.parseNumber(number_y);
    if (utils.anyIsError(number_x, number_y)) {
      return error.value;
    }
    return Math.atan2(number_x, number_y);
  };
  
  exports.ATANH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var result = Math.log((1 + number) / (1 - number)) / 2;
  
    if (isNaN(result)) {
      result = error.num;
    }
  
    return result;
  };
  
  exports.BASE = function(number, radix, min_length) {
    min_length = min_length || 0;
  
    number = utils.parseNumber(number);
    radix = utils.parseNumber(radix);
    min_length = utils.parseNumber(min_length);
    if (utils.anyIsError(number, radix, min_length)) {
      return error.value;
    }
    min_length = (min_length === undefined) ? 0 : min_length;
    var result = number.toString(radix);
    return new Array(Math.max(min_length + 1 - result.length, 0)).join('0') + result;
  };
  
  exports.CEILING = function(number, significance, mode) {
    significance = (significance === undefined) ? 1 : Math.abs(significance);
    mode = mode || 0;
  
    number = utils.parseNumber(number);
    significance = utils.parseNumber(significance);
    mode = utils.parseNumber(mode);
    if (utils.anyIsError(number, significance, mode)) {
      return error.value;
    }
    if (significance === 0) {
      return 0;
    }
    var precision = -Math.floor(Math.log(significance) / Math.log(10));
    if (number >= 0) {
      return exports.ROUND(Math.ceil(number / significance) * significance, precision);
    } else {
      if (mode === 0) {
        return -exports.ROUND(Math.floor(Math.abs(number) / significance) * significance, precision);
      } else {
        return -exports.ROUND(Math.ceil(Math.abs(number) / significance) * significance, precision);
      }
    }
  };
  
  exports.CEILING.MATH = exports.CEILING;
  
  exports.CEILING.PRECISE = exports.CEILING;
  
  exports.COMBIN = function(number, number_chosen) {
    number = utils.parseNumber(number);
    number_chosen = utils.parseNumber(number_chosen);
    if (utils.anyIsError(number, number_chosen)) {
      return error.value;
    }
    return exports.FACT(number) / (exports.FACT(number_chosen) * exports.FACT(number - number_chosen));
  };
  
  exports.COMBINA = function(number, number_chosen) {
    number = utils.parseNumber(number);
    number_chosen = utils.parseNumber(number_chosen);
    if (utils.anyIsError(number, number_chosen)) {
      return error.value;
    }
    return (number === 0 && number_chosen === 0) ? 1 : exports.COMBIN(number + number_chosen - 1, number - 1);
  };
  
  exports.COS = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.cos(number);
  };
  
  exports.COSH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return (Math.exp(number) + Math.exp(-number)) / 2;
  };
  
  exports.COT = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return 1 / Math.tan(number);
  };
  
  exports.COTH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var e2 = Math.exp(2 * number);
    return (e2 + 1) / (e2 - 1);
  };
  
  exports.CSC = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return 1 / Math.sin(number);
  };
  
  exports.CSCH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return 2 / (Math.exp(number) - Math.exp(-number));
  };
  
  exports.DECIMAL = function(number, radix) {
    if (arguments.length < 1) {
      return error.value;
    }
  
    return parseInt(number, radix);
  };
  
  exports.DEGREES = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return number * 180 / Math.PI;
  };
  
  exports.EVEN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return exports.CEILING(number, -2, -1);
  };
  
  exports.EXP = function(number) {
    if (arguments.length < 1) {
      return error.na;
    }
    if (typeof number !== 'number' || arguments.length > 1) {
      return error.error;
    }
  
    number = Math.exp(number);
  
    return number;
  };
  
  var MEMOIZED_FACT = [];
  exports.FACT = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var n = Math.floor(number);
    if (n === 0 || n === 1) {
      return 1;
    } else if (MEMOIZED_FACT[n] > 0) {
      return MEMOIZED_FACT[n];
    } else {
      MEMOIZED_FACT[n] = exports.FACT(n - 1) * n;
      return MEMOIZED_FACT[n];
    }
  };
  
  exports.FACTDOUBLE = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var n = Math.floor(number);
    if (n <= 0) {
      return 1;
    } else {
      return n * exports.FACTDOUBLE(n - 2);
    }
  };
  
  exports.FLOOR = function(number, significance) {
    number = utils.parseNumber(number);
    significance = utils.parseNumber(significance);
    if (utils.anyIsError(number, significance)) {
      return error.value;
    }
    if (significance === 0) {
      return 0;
    }
  
    if (!(number > 0 && significance > 0) && !(number < 0 && significance < 0)) {
      return error.num;
    }
  
    significance = Math.abs(significance);
    var precision = -Math.floor(Math.log(significance) / Math.log(10));
    if (number >= 0) {
      return exports.ROUND(Math.floor(number / significance) * significance, precision);
    } else {
      return -exports.ROUND(Math.ceil(Math.abs(number) / significance), precision);
    }
  };
  
  //TODO: Verify
  exports.FLOOR.MATH = function(number, significance, mode) {
    significance = (significance === undefined) ? 1 : significance;
    mode = (mode === undefined) ? 0 : mode;
  
    number = utils.parseNumber(number);
    significance = utils.parseNumber(significance);
    mode = utils.parseNumber(mode);
    if (utils.anyIsError(number, significance, mode)) {
      return error.value;
    }
    if (significance === 0) {
      return 0;
    }
  
    significance = significance ? Math.abs(significance) : 1;
    var precision = -Math.floor(Math.log(significance) / Math.log(10));
    if (number >= 0) {
      return exports.ROUND(Math.floor(number / significance) * significance, precision);
    } else if (mode === 0 || mode === undefined) {
      return -exports.ROUND(Math.ceil(Math.abs(number) / significance) * significance, precision);
    }
    return -exports.ROUND(Math.floor(Math.abs(number) / significance) * significance, precision);
  };
  
  // Deprecated
  exports.FLOOR.PRECISE = exports.FLOOR.MATH;
  
  // adapted http://rosettacode.org/wiki/Greatest_common_divisor#JavaScript
  exports.GCD = function() {
    var range = utils.parseNumberArray(utils.flatten(arguments));
    if (range instanceof Error) {
      return range;
    }
    var n = range.length;
    var r0 = range[0];
    var x = r0 < 0 ? -r0 : r0;
    for (var i = 1; i < n; i++) {
      var ri = range[i];
      var y = ri < 0 ? -ri : ri;
      while (x && y) {
        if (x > y) {
          x %= y;
        } else {
          y %= x;
        }
      }
      x += y;
    }
    return x;
  };
  
  
  exports.INT = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.floor(number);
  };
  
  //TODO: verify
  exports.ISO = {
    CEILING: exports.CEILING
  };
  
  exports.LCM = function() {
    // Credits: Jonas Raoni Soares Silva
    var o = utils.parseNumberArray(utils.flatten(arguments));
    if (o instanceof Error) {
      return o;
    }
    for (var i, j, n, d, r = 1;
         (n = o.pop()) !== undefined;) {
      while (n > 1) {
        if (n % 2) {
          for (i = 3, j = Math.floor(Math.sqrt(n)); i <= j && n % i; i += 2) {
            //empty
          }
          d = (i <= j) ? i : n;
        } else {
          d = 2;
        }
        for (n /= d, r *= d, i = o.length; i;
             (o[--i] % d) === 0 && (o[i] /= d) === 1 && o.splice(i, 1)) {
          //empty
        }
      }
    }
    return r;
  };
  
  exports.LN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.log(number);
  };
  
  exports.LN10 = function() {
    return Math.log(10);
  };
  
  exports.LN2 = function() {
    return Math.log(2);
  };
  
  exports.LOG10E = function() {
    return Math.LOG10E;
  };
  
  exports.LOG2E = function() {
    return Math.LOG2E;
  };
  
  exports.LOG = function(number, base) {
    number = utils.parseNumber(number);
    base = utils.parseNumber(base);
    if (utils.anyIsError(number, base)) {
      return error.value;
    }
    base = (base === undefined) ? 10 : base;
    return Math.log(number) / Math.log(base);
  };
  
  exports.LOG10 = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.log(number) / Math.log(10);
  };
  
  exports.MOD = function(dividend, divisor) {
    dividend = utils.parseNumber(dividend);
    divisor = utils.parseNumber(divisor);
    if (utils.anyIsError(dividend, divisor)) {
      return error.value;
    }
    if (divisor === 0) {
      return error.div0;
    }
    var modulus = Math.abs(dividend % divisor);
    return (divisor > 0) ? modulus : -modulus;
  };
  
  exports.MROUND = function(number, multiple) {
    number = utils.parseNumber(number);
    multiple = utils.parseNumber(multiple);
    if (utils.anyIsError(number, multiple)) {
      return error.value;
    }
    if (number * multiple < 0) {
      return error.num;
    }
  
    return Math.round(number / multiple) * multiple;
  };
  
  exports.MULTINOMIAL = function() {
    var args = utils.parseNumberArray(utils.flatten(arguments));
    if (args instanceof Error) {
      return args;
    }
    var sum = 0;
    var divisor = 1;
    for (var i = 0; i < args.length; i++) {
      sum += args[i];
      divisor *= exports.FACT(args[i]);
    }
    return exports.FACT(sum) / divisor;
  };
  
  exports.ODD = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var temp = Math.ceil(Math.abs(number));
    temp = (temp & 1) ? temp : temp + 1;
    return (number > 0) ? temp : -temp;
  };
  
  exports.PI = function() {
    return Math.PI;
  };
  
  exports.E = function() {
    return Math.E;
  };
  
  exports.POWER = function(number, power) {
    number = utils.parseNumber(number);
    power = utils.parseNumber(power);
    if (utils.anyIsError(number, power)) {
      return error.value;
    }
    var result = Math.pow(number, power);
    if (isNaN(result)) {
      return error.num;
    }
  
    return result;
  };
  
  exports.PRODUCT = function() {
    var args = utils.parseNumberArray(utils.flatten(arguments));
    if (args instanceof Error) {
      return args;
    }
    var result = 1;
    for (var i = 0; i < args.length; i++) {
      result *= args[i];
    }
    return result;
  };
  
  exports.QUOTIENT = function(numerator, denominator) {
    numerator = utils.parseNumber(numerator);
    denominator = utils.parseNumber(denominator);
    if (utils.anyIsError(numerator, denominator)) {
      return error.value;
    }
    return parseInt(numerator / denominator, 10);
  };
  
  exports.RADIANS = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return number * Math.PI / 180;
  };
  
  exports.RAND = function() {
    return Math.random();
  };
  
  exports.RANDBETWEEN = function(bottom, top) {
    bottom = utils.parseNumber(bottom);
    top = utils.parseNumber(top);
    if (utils.anyIsError(bottom, top)) {
      return error.value;
    }
    // Creative Commons Attribution 3.0 License
    // Copyright (c) 2012 eqcode
    return bottom + Math.ceil((top - bottom + 1) * Math.random()) - 1;
  };
  
  // TODO
  exports.ROMAN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    // The MIT License
    // Copyright (c) 2008 Steven Levithan
    var digits = String(number).split('');
    var key = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
    var roman = '';
    var i = 3;
    while (i--) {
      roman = (key[+digits.pop() + (i * 10)] || '') + roman;
    }
    return new Array(+digits.join('') + 1).join('M') + roman;
  };
  
  exports.ROUND = function(number, digits) {
    number = utils.parseNumber(number);
    digits = utils.parseNumber(digits);
    if (utils.anyIsError(number, digits)) {
      return error.value;
    }
    return Math.round(number * Math.pow(10, digits)) / Math.pow(10, digits);
  };
  
  exports.ROUNDDOWN = function(number, digits) {
    number = utils.parseNumber(number);
    digits = utils.parseNumber(digits);
    if (utils.anyIsError(number, digits)) {
      return error.value;
    }
    var sign = (number > 0) ? 1 : -1;
    return sign * (Math.floor(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
  };
  
  exports.ROUNDUP = function(number, digits) {
    number = utils.parseNumber(number);
    digits = utils.parseNumber(digits);
    if (utils.anyIsError(number, digits)) {
      return error.value;
    }
    var sign = (number > 0) ? 1 : -1;
    return sign * (Math.ceil(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
  };
  
  exports.SEC = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return 1 / Math.cos(number);
  };
  
  exports.SECH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return 2 / (Math.exp(number) + Math.exp(-number));
  };
  
  exports.SERIESSUM = function(x, n, m, coefficients) {
    x = utils.parseNumber(x);
    n = utils.parseNumber(n);
    m = utils.parseNumber(m);
    coefficients = utils.parseNumberArray(coefficients);
    if (utils.anyIsError(x, n, m, coefficients)) {
      return error.value;
    }
    var result = coefficients[0] * Math.pow(x, n);
    for (var i = 1; i < coefficients.length; i++) {
      result += coefficients[i] * Math.pow(x, n + i * m);
    }
    return result;
  };
  
  exports.SIGN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    if (number < 0) {
      return -1;
    } else if (number === 0) {
      return 0;
    } else {
      return 1;
    }
  };
  
  exports.SIN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.sin(number);
  };
  
  exports.SINH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return (Math.exp(number) - Math.exp(-number)) / 2;
  };
  
  exports.SQRT = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    if (number < 0) {
      return error.num;
    }
    return Math.sqrt(number);
  };
  
  exports.SQRTPI = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.sqrt(number * Math.PI);
  };
  
  exports.SQRT1_2 = function() {
    return 1 / Math.sqrt(2);
  };
  
  exports.SQRT2 = function() {
    return Math.sqrt(2);
  };
  
  exports.SUBTOTAL = function(function_code, ref1) {
    function_code = utils.parseNumber(function_code);
    if (function_code instanceof Error) {
      return function_code;
    }
    switch (function_code) {
      case 1:
        return statistical.AVERAGE(ref1);
      case 2:
        return statistical.COUNT(ref1);
      case 3:
        return statistical.COUNTA(ref1);
      case 4:
        return statistical.MAX(ref1);
      case 5:
        return statistical.MIN(ref1);
      case 6:
        return exports.PRODUCT(ref1);
      case 7:
        return statistical.STDEV.S(ref1);
      case 8:
        return statistical.STDEV.P(ref1);
      case 9:
        return exports.SUM(ref1);
      case 10:
        return statistical.VAR.S(ref1);
      case 11:
        return statistical.VAR.P(ref1);
      // no hidden values for us
      case 101:
        return statistical.AVERAGE(ref1);
      case 102:
        return statistical.COUNT(ref1);
      case 103:
        return statistical.COUNTA(ref1);
      case 104:
        return statistical.MAX(ref1);
      case 105:
        return statistical.MIN(ref1);
      case 106:
        return exports.PRODUCT(ref1);
      case 107:
        return statistical.STDEV.S(ref1);
      case 108:
        return statistical.STDEV.P(ref1);
      case 109:
        return exports.SUM(ref1);
      case 110:
        return statistical.VAR.S(ref1);
      case 111:
        return statistical.VAR.P(ref1);
  
    }
  };
  
  exports.ADD = function (num1, num2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    num1 = utils.parseNumber(num1);
    num2 = utils.parseNumber(num2);
    if (utils.anyIsError(num1, num2)) {
      return error.value;
    }
  
    return num1 + num2;
  };
  
  exports.MINUS = function (num1, num2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    num1 = utils.parseNumber(num1);
    num2 = utils.parseNumber(num2);
    if (utils.anyIsError(num1, num2)) {
      return error.value;
    }
  
    return num1 - num2;
  };
  
  exports.DIVIDE = function (dividend, divisor) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    dividend = utils.parseNumber(dividend);
    divisor = utils.parseNumber(divisor);
    if (utils.anyIsError(dividend, divisor)) {
      return error.value;
    }
  
    if (divisor === 0) {
      return error.div0;
    }
  
    return dividend / divisor;
  };
  
  exports.MULTIPLY = function (factor1, factor2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    factor1 = utils.parseNumber(factor1);
    factor2 = utils.parseNumber(factor2);
    if (utils.anyIsError(factor1, factor2)) {
      return error.value;
    }
  
    return factor1 * factor2;
  };
  
  exports.GTE = function (num1, num2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    num1 = utils.parseNumber(num1);
    num2 = utils.parseNumber(num2);
    if (utils.anyIsError(num1, num2)) {
      return error.error;
    }
  
    return num1 >= num2;
  };
  
  exports.LT = function (num1, num2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    num1 = utils.parseNumber(num1);
    num2 = utils.parseNumber(num2);
    if (utils.anyIsError(num1, num2)) {
      return error.error;
    }
  
    return num1 < num2;
  };
  
  
  exports.LTE = function (num1, num2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    num1 = utils.parseNumber(num1);
    num2 = utils.parseNumber(num2);
    if (utils.anyIsError(num1, num2)) {
      return error.error;
    }
  
    return num1 <= num2;
  };
  
  exports.EQ = function (value1, value2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    return value1 === value2;
  };
  
  exports.NE = function (value1, value2) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    return value1 !== value2;
  };
  
  exports.POW = function (base, exponent) {
    if (arguments.length !== 2) {
      return error.na;
    }
  
    base = utils.parseNumber(base);
    exponent = utils.parseNumber(exponent);
    if (utils.anyIsError(base, exponent)) {
      return error.error;
    }
  
    return exports.POWER(base, exponent);
  };
  
  exports.SUM = function() {
    var result = 0;
  
    utils.arrayEach(utils.argsToArray(arguments), function(value) {
      if (typeof value === 'number') {
        result += value;
  
      } else if (typeof value === 'string') {
        var parsed = parseFloat(value);
  
        !isNaN(parsed) && (result += parsed);
  
      } else if (Array.isArray(value)) {
        result += exports.SUM.apply(null, value);
      }
    });
  
    return result;
  };
  
  exports.SUMIF = function(range, criteria) {
    range = utils.parseNumberArray(utils.flatten(range));
  
    if (range instanceof Error) {
      return range;
    }
    var result = 0;
    var isWildcard = criteria === void 0 || criteria === '*';
    var tokenizedCriteria = isWildcard ? null : evalExpression.parse(criteria + '');
  
    for (var i = 0; i < range.length; i++) {
      var value = range[i];
  
      if (isWildcard) {
        result += value;
      } else {
        var tokens = [evalExpression.createToken(value, evalExpression.TOKEN_TYPE_LITERAL)].concat(tokenizedCriteria);
  
        result += (evalExpression.compute(tokens) ? value : 0);
      }
    }
  
    return result;
  };
  
  exports.SUMIFS = function() {
    var args = utils.argsToArray(arguments);
    var range = utils.parseNumberArray(utils.flatten(args.shift()));
  
    if (range instanceof Error) {
      return range;
    }
    var criterias = args;
    var n_range_elements = range.length;
    var criteriaLength = criterias.length;
    var result = 0;
  
    for (var i = 0; i < n_range_elements; i++) {
      var value = range[i];
      var isMeetCondition = false;
  
      for (var j = 0; j < criteriaLength; j++) {
        var criteria = criterias[j];
        var isWildcard = criteria === void 0 || criteria === '*';
        var computedResult = false;
  
        if (isWildcard) {
          computedResult = true;
        } else {
          var tokenizedCriteria = evalExpression.parse(criteria + '');
          var tokens = [evalExpression.createToken(value, evalExpression.TOKEN_TYPE_LITERAL)].concat(tokenizedCriteria);
  
          computedResult = evalExpression.compute(tokens);
        }
  
        // Criterias are calculated as AND so any `false` breakes the loop as unmeet condition
        if (!computedResult) {
          isMeetCondition = false;
          break;
        }
  
        isMeetCondition = true;
      }
  
      if (isMeetCondition) {
        result += value;
      }
    }
  
    return result;
  };
  
  exports.SUMPRODUCT = function() {
    if (!arguments || arguments.length === 0) {
      return error.value;
    }
    var arrays = arguments.length + 1;
    var result = 0;
    var product;
    var k;
    var _i;
    var _ij;
    for (var i = 0; i < arguments[0].length; i++) {
      if (!(arguments[0][i] instanceof Array)) {
        product = 1;
        for (k = 1; k < arrays; k++) {
          _i = utils.parseNumber(arguments[k - 1][i]);
          if (_i instanceof Error) {
            return _i;
          }
          product *= _i;
        }
        result += product;
      } else {
        for (var j = 0; j < arguments[0][i].length; j++) {
          product = 1;
          for (k = 1; k < arrays; k++) {
            _ij = utils.parseNumber(arguments[k - 1][i][j]);
            if (_ij instanceof Error) {
              return _ij;
            }
            product *= _ij;
          }
          result += product;
        }
      }
    }
    return result;
  };
  
  exports.SUMSQ = function() {
    var numbers = utils.parseNumberArray(utils.flatten(arguments));
    if (numbers instanceof Error) {
      return numbers;
    }
    var result = 0;
    var length = numbers.length;
    for (var i = 0; i < length; i++) {
      result += (information.ISNUMBER(numbers[i])) ? numbers[i] * numbers[i] : 0;
    }
    return result;
  };
  
  exports.SUMX2MY2 = function(array_x, array_y) {
    array_x = utils.parseNumberArray(utils.flatten(array_x));
    array_y = utils.parseNumberArray(utils.flatten(array_y));
    if (utils.anyIsError(array_x, array_y)) {
      return error.value;
    }
    var result = 0;
    for (var i = 0; i < array_x.length; i++) {
      result += array_x[i] * array_x[i] - array_y[i] * array_y[i];
    }
    return result;
  };
  
  exports.SUMX2PY2 = function(array_x, array_y) {
    array_x = utils.parseNumberArray(utils.flatten(array_x));
    array_y = utils.parseNumberArray(utils.flatten(array_y));
    if (utils.anyIsError(array_x, array_y)) {
      return error.value;
    }
    var result = 0;
    array_x = utils.parseNumberArray(utils.flatten(array_x));
    array_y = utils.parseNumberArray(utils.flatten(array_y));
    for (var i = 0; i < array_x.length; i++) {
      result += array_x[i] * array_x[i] + array_y[i] * array_y[i];
    }
    return result;
  };
  
  exports.SUMXMY2 = function(array_x, array_y) {
    array_x = utils.parseNumberArray(utils.flatten(array_x));
    array_y = utils.parseNumberArray(utils.flatten(array_y));
    if (utils.anyIsError(array_x, array_y)) {
      return error.value;
    }
    var result = 0;
    array_x = utils.flatten(array_x);
    array_y = utils.flatten(array_y);
    for (var i = 0; i < array_x.length; i++) {
      result += Math.pow(array_x[i] - array_y[i], 2);
    }
    return result;
  };
  
  exports.TAN = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    return Math.tan(number);
  };
  
  exports.TANH = function(number) {
    number = utils.parseNumber(number);
    if (number instanceof Error) {
      return number;
    }
    var e2 = Math.exp(2 * number);
    return (e2 - 1) / (e2 + 1);
  };
  
  exports.TRUNC = function(number, digits) {
    digits = (digits === undefined) ? 0 : digits;
    number = utils.parseNumber(number);
    digits = utils.parseNumber(digits);
    if (utils.anyIsError(number, digits)) {
      return error.value;
    }
    var sign = (number > 0) ? 1 : -1;
    return sign * (Math.floor(Math.abs(number) * Math.pow(10, digits))) / Math.pow(10, digits);
  };
  