A paper is proposing that most elementary functions such as sin, cos, 1/x can be expressed as a combination of the following function and the constant 1.

eml(x,y)=exp(x)ln(y)eml(x,y) = exp(x) – ln(y)

For example ln(x) can be written as eml(1, eml(eml(1, x), 1)). Let’s do a few experiments with this.

A Basic Test

Let’s write the EML function.

#include <iostream>
#include <cmath>

double eml(double x, double y) {
    return std::exp(x) - std::log(y);
}

We can now calculate and print ln(x) using the EML function.

int main() {
    double x = 10.23189; //Sample input
    double r = eml(1, eml(eml(1, x), 1));
    double expected = std::log(x);

    std::cout << r << " " << expected << std::endl;

    return 0;
}

This should print.

An RPN Expression Evaluator

We can express the EML combination for ln(x) using the RPN notation 11xE1EE where E is the EML function operator. Let’s write a C++ function that evaluates such an RPN expression.

#include <stack>

bool eval_rpn(const char* rpn, double x, double& result) {
    std::stack<double> s;

    while (*rpn != '\0') {
        if (*rpn == '1') {
            s.push(1.0);
        } else if (*rpn == 'x') {
            s.push(x);
        } else if (*rpn == 'E') {
            //Pop the last two values
            if (s.size() < 2) {
                return false; //Invalid expression
            }

            double vy = s.top();

            s.pop();

            double vx = s.top();

            s.pop();

            //Push the EML result
            s.push(eml(vx, vy));
        } else {
            return false; //Invalid expression
        }

        ++rpn;
    }

    if (s.size() != 1) {
        return false;
    }

    result = s.top();

    return true;
}

We can now use this function.

int main() {
    double x = 10.23189; //Sample input
    double expected = std::log(x);
    double r;

    if (eval_rpn("11xE1EE", x, r)) {
        std::cout << r << " " << expected << std::endl;
    } else {
        std::cout << "RPN evaluation failed." << std::endl;
    }

    return 0;
}

A Few More Examples

We can write a generic function that evaluates an RPN expression and prints out the result next to the expected values.

void run_test(double x, double expected, const char* rpn) {
    double r;

    if (eval_rpn(rpn, x, r)) {
        std::cout << r << " " << expected << std::endl;
    } else {
        std::cout << "RPN evaluation failed." << std::endl;
    }
}

We can now test out a few elimentary functions.

int main() {
    
    //ln(x)
    run_test(100.23189, std::log(100.23189), "11xE1EE");

    //x
    run_test(89.12, 89.12, "11x1EE1EE");

    //-x
    run_test(89.12, -89.12, "11111E1EEEEx1EE");

    //1/x
    run_test(15.0, 1/15.0, "11111E1EEEExE1E");

    return 0;
}

Binary Operations

We can make small changes to support binary operations like x * y.

bool eval_rpn(const char* rpn, double x, double y, double& result) {
    std::stack<double> s;

    while (*rpn != '\0') {
        if (*rpn == '1') {
            s.push(1.0);
        } else if (*rpn == 'x') {
            s.push(x);
        } else if (*rpn == 'y') {
            s.push(y);
        } else if (*rpn == 'E') {
            //Pop the last two values
            if (s.size() < 2) {
                return false; //Invalid expression
            }

            double vy = s.top();

            s.pop();

            double vx = s.top();

            s.pop();

            //Push the EML result
            s.push(eml(vx, vy));
        } else {
            return false; //Invalid expression
        }

        ++rpn;
    }

    if (s.size() != 1) {
        return false;
    }

    result = s.top();

    return true;
}

void run_test(double x, double y, double expected, const char* rpn) {
    double r;

    if (eval_rpn(rpn, x, y, r)) {
        std::cout << r << " " << expected << std::endl;
    } else {
        std::cout << "RPN evaluation failed." << std::endl;
    }
}

int main() {
    //x * y
    run_test(15.0, 2.0, 15.0 * 2.0, "1111xEE1EE11y1EE1EEE1EE1E");

    return 0;
}