        var isNumber = function isNumber(value)
        {
           return typeof value === 'number' && isFinite(value);
        }

        var calcConstants = {
                    'pi': Math.PI,
                    'e': Math.E,
                    '-pi': -Math.PI,
                    '-e': -Math.E
                };

        /**
          * Преобразование выражения в обратную польскую нотацию
          * @param data
          * @returns {*}
        */
        function RPN(data) {

            // Стек, используемый для временного хранения операторов.
            // К нему сразу же прикрепляем метод наш last,
            // который возвращает последний элемент стека
            var stack = [];
            stack.last = function() { stack[stack.length - 1] };

            // Основной стек, который возвращается из функции RPN
            var output = [];

            // Текущие элемент массива
            var t;

            // Последовательно рассматриваем каждый элемент полученного массива
            for (var i = 0; data[i] !== undefined; i++) {

                t = data[i];

                if (isNumber(t) || Object.keys(calcConstants).indexOf(t) > -1) {
                    // Число заносим сразу в выходной массив
                    output.push(t);

                } else if (isOperator2(t) || isOperator1(t)) {

                    // Объявляем формулу сравнения в зависимости от
                    // ассоциативности текущей операции
                    var opCompare = rightAssociativity.indexOf(t) > -1 ?
                        function() { opWeight[stack.last()] > opWeight[t] } :
                        function() { opWeight[stack.last()] >= opWeight[t] };

                    // Переносим элементы из временного стека в выходной
                    while (stack.length > 0 && opCompare())
                        output.push(stack.pop());

                    // Помещаем текущий элемент на хранение во временный стек
                    stack.push(t);

                } else if (t === '(') {
                    // Заносим '(' в стек
                    stack.push(t);

                } else if (t === ')') {
                    // Переносим операторы из стека до открывающей скобки
                    var operator;

                    while (stack.length > 0) {
                        operator = stack.pop();
                        if (operator !== '(')
                            output.push(operator);
                        else
                            break;
                    }

                } else throw 'ОШИБКА: нераспознанное выражение '+t+'\nРекомендуется изучить лог операций';

            }

            // Записываем в выходной массив оставшиеся операции
            while (stack.length > 0)
                output.push(stack.pop());

            return output;
        }

        /**
         * Расчёт выражения
         * @param exp
         * @returns {float}
         */
        function calculate(exp) {

            var stack = [];
            // Прикрепляем к массиву-стеку stack метод pushFloat,
            // который получает строку, переводит её в число,
            // чуть округляет его для избавления от ошибок вычисления
            // и помещает в конец массива-стека stack )
            stack.pushFloat =
                function(value) { stack.push(parseFloat(parseFloat(value).toFixed(15))) };

            // Если выражение не задано
            // то генерируем ошибку с пояснительным текстом
            if (!exp) throw "ОШИБКА: выражение не задано";

            // Если в выражении есть факториалы, то перемещаем их вперёд числа
            exp = exp.replace(
                new RegExp('(-?\\d+\\.?\\d*)\\s*(!+)', 'g'),
                '$2 $1'
            );

            // Преобразование выражения в обратную польскую нотацию
            var data =  RPN(exp.split(/[\s]+/));

            // Если выражение состоит из одного числа и у нас есть запомненная операция, то применяем её.
            // Иначе если введена лишь одна простая операция, то запоминаем её.
            // Иначе удаляем из памяти запомненную операцию, чтобы не путала
            if (data.length == 1 && isNumberOrConstant(data[0]) && lastOperationStore.length > 0) {

                logWindowOut('Повторение последней операции:');
                if (lastOperationStore.length==2)
                    logWindowOut(data[0]+' '+lastOperationStore[1]+' '+lastOperationStore[0])
                else if (lastOperationStore[0]=='!')
                    logWindowOut(data[0]+' !')
                else
                    logWindowOut(lastOperationStore[0]+' '+data[0]);
                data = data.concat(lastOperationStore);

            } else {

                if (data.length == 2 && isOperator1(data[1]) && isNumberOrConstant(data[0])) {
                    lastOperationStore = [data[1]];
                } else if (data.length == 3 && isOperator2(data[2]) && isNumberOrConstant(data[1]) && isNumberOrConstant(data[0])) {
                    lastOperationStore = [data[1],data[2]];
                } else
                    lastOperationStore = [];

                // Выводим в лог-окно получившуюся обратную польскую запись
                logWindowOut('Обратная польская запись:',data);
            }

            // Идём по массиву data.
            // В случае обнаружения числа заносим его в массив-стек stack.
            // В случае обнаружения оператора изымаем из стека два последних числа,
            // производим с ними операцию, прописанную в объекте calcMethods и
            // помещаем результат в конец стека stack
            data.forEach(function(item, i, arr) {

                if (isNumber(item))
                    // Заносим числа сразу в стек
                    stack.pushFloat(item);

                else if (isConstant(item))
                    // Вместо мат.константы заносим в стек её значение
                    stack.push(calcConstants[item]);

                else if (isOperator1(item)) {

                    // Выдаём ошибку если недостаточно операндов
                    // в стеке для выполнения текущей операции
                    if (stack.length < 1)
                        throw 'ОШИБКА: предполагается пропуск числа или лишний оператор';

                    // Запускаем расчёты
                    stack.pushFloat(
                        calcMethods1[item](stack.pop())
                    );

                } else if (isOperator2(item)) {

                    // Выдаём ошибку если не достаточно операндов
                    // в стеке для выполнения текущей операции
                    if (stack.length < 2)
                        throw 'ОШИБКА: предполагается пропуск числа или лишний оператор';

                    // Запускаем расчёты
                    stack.pushFloat(
                        calcMethods2[item](stack.pop(), stack.pop())
                    );

                    // Это исключение никогда не должно сработать, поскольку все символы
                    // уже профильтрованы в функции RPN, но лучше перестраховаться
                } else throw 'ОШИБКА: нераспознанное выражение '+item+' в функции calculate';

            });

            // Если в стеке остался не один операнд,
            // то генерируем ошибку с пояснительным текстом
            if (stack.length > 1)
                throw 'ОШИБКА: предполагается наличие лишнего числа или пропуск оператора';

            // Округляем ответ до меньшей точности.
            // Если этого не сделать, то, к примеру,
            // вычисление кубического корня из 1000 вернёт 9.999999999999975
            stack[0] = parseFloat(stack[0].toFixed(12));

            // Выводим в лог-окно ответ
            logWindowOut('Ответ: &nbsp;'+stack[0]);
            return stack.pop();
        }