Example #1
0
/**
 * Evaluates the given mathematical expression and returns the result.
 * Angles may be expressed in degrees (default), rad (#r), gon (#g)
 * or as surveyors angles (N#d#'#"E).
 */
double RMath::eval(const QString& expression, bool* ok) {
    lastError = "";

    if (expression.isEmpty()) {
        if (ok!=NULL) {
            *ok = false;
        }
        lastError = "Expression is empty";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    if (ok!=NULL) {
        *ok = true;
    }

    QString expr = expression;

    // 'correct' commas to points:
    if (RSettings::getNumberLocale().decimalPoint()==',') {
        expr.replace(',', '.');
    }

    if (expr.contains(',') || expr.contains(';')) {
        if (ok!=NULL) {
            *ok = false;
        }
        lastError = "Multiple expressions";
        return RNANDOUBLE;
    }

    int idx = -1;

    // convert surveyor type angles (e.g. N10d30'12.5"E) to degrees:
    if (expr.contains(QRegExp("[NESW]", Qt::CaseInsensitive))) {
        // \b(?:(?:([NS])(?:([+-]?)(?:(?:(\d*\.?\d*)[d°])?(?:(\d*\.?\d*)')?(?:(\d*\.?\d*)")?|(\d*))([EW]))?)|([EW]))\b
        QRegExp re(
            "\\b"                               // a word
            "(?:"
              "(?:"
                "([NS])"                        // expression starts with nord or south
                "(?:"
                  "([+-]?)"                     // sign
                  "(?:"
                    "(?:(\\d*\\.?\\d*)[d°])?"   // degrees with d
                    "(?:(\\d*\\.?\\d*)')?"      // minutes with '
                    "(?:(\\d*\\.?\\d*)\")?"     // seconds with "
                    "|"                         // or...
                    "(\\d*)"                    // degrees without d
                  ")"
                  "([EW])"                      // east or west
                ")?"
              ")"
              "|"                               // or...
              "([EW])"                          // only east (0d) or west (180d)
            ")"
            "\\b",
            Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            double angle = 0.0;
            QString sign;

            // "E" or "W":
            if (!re.cap(8).isEmpty()) {
                if (re.cap(8).toUpper()=="E") {
                    angle = 0.0;
                }
                else if (re.cap(8).toUpper()=="W") {
                    angle = 180.0;
                }
                else {
                    if (ok!=NULL) {
                        *ok = false;
                    }
                    lastError = "Invalid cardinal direction found (valid values are: N,E,S,W)";
                    return RNANDOUBLE;
                }
            }
            // "[NS]...[EW]":
            else {
                bool north = re.cap(1).toUpper()=="N";
                bool south = re.cap(1).toUpper()=="S";
                sign = re.cap(2);
                double degrees = 0.0;
                double minutes = 0.0;
                double seconds = 0.0;
                if (!re.cap(6).isEmpty()) {
                    degrees = re.cap(6).toDouble(ok);
                }
                else {
                    degrees = re.cap(3).toDouble(ok);
                    minutes = re.cap(4).toDouble(ok);
                    seconds = re.cap(5).toDouble(ok);
                }
                bool east = re.cap(7).toUpper()=="E";
                bool west = re.cap(7).toUpper()=="W";

                double base = (north ? 90.0 : 270.0);
                int dir = ((north && west) || (south && east) ? 1 : -1);
                angle = base + dir * (degrees + minutes/60.0 + seconds/3600.0);
            }

            expr.replace(
                re.cap(),
                QString("%1%2").arg(sign).arg(angle, 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 001 is: " << expr;

    // convert radiant angles (e.g. "1.2r") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))r\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            //qDebug() << "RMath::eval: match 001a is: " << match;
            expr.replace(
                re,
                QString("%1").arg(rad2deg(match.toDouble(ok)), 0, 'g', 16)
            );
            //qDebug() << "RMath::eval: expression 001a is: " << expr;
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 002 is: " << expr;

    // convert grad angles (e.g. "100g") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))g\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            expr.replace(
                re,
                QString("%1").arg(gra2deg(match.toDouble(ok)), 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 003 is: " << expr;

    // convert explicitely indicated degree angles (e.g. "90d") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))d\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            expr.replace(
                re,
                QString("%1").arg(match.toDouble(ok), 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    // convert fraction notation to formula:
    // e.g. 7 3/32 to 7+3/32
    {
        QRegExp re("(\\d+)[ ]+(\\d+/\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString numberString = re.cap(1);
            QString fractionString = re.cap(2);
            expr.replace(
                re,
                QString("%1+%2").arg(numberString).arg(fractionString)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression is: " << expr;

    QScriptEngine e;

    // add common functions of Math to global scope for use in math input fields:
    QStringList mathProp;
    mathProp  << "PI" << "LN2" << "LN10" << "LOG2E" << "LOG10E" << "SQRT1_2" << "SQRT2"
          << "abs"
          //<< "cos" << "sin" << "tan"
          //<< "acos" << "asin" << "atan" << "atan2"
          << "ceil" << "floor"
          << "exp" << "log"
          << "max" << "min"
          << "pow" << "sqrt"
          << "random"
          << "round";
    for (int i=0; i<mathProp.length(); i++) {
        e.evaluate(mathProp[i] + " = Math." + mathProp[i]);
    }

    e.evaluate("rad2deg = function(a) { return a / (2.0 * Math.PI) * 360.0; }");
    e.evaluate("deg2rad = function(a) { return (a / 360.0) * (2.0 * Math.PI); }");

    e.evaluate("sin = function(v) { return Math.sin(deg2rad(v)); }");
    e.evaluate("cos = function(v) { return Math.cos(deg2rad(v)); }");
    e.evaluate("tan = function(v) { return Math.tan(deg2rad(v)); }");

    e.evaluate("asin = function(v) { return rad2deg(Math.asin(v)); }");
    e.evaluate("acos = function(v) { return rad2deg(Math.acos(v)); }");
    e.evaluate("atan = function(v) { return rad2deg(Math.atan(v)); }");
    //e.evaluate("atan2 = function(y,x) { return rad2deg(Math.atan2(y,x)); }");

    QScriptValue res = e.evaluate(expr);

    if (res.isError()) {
        if (ok!=NULL) {
            *ok = false;
        }
        lastError = res.toString();
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    if (!res.isNumber()) {
        if (ok != NULL) {
            *ok = false;
        }
        lastError = expr + " is not a number";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    //qDebug() << "res.toNumber(): " << res.toNumber();
    //qDebug() << "fpclassify: " << std::fpclassify(res.toNumber());

    if (!isNormal(res.toNumber())) {
        if (ok != NULL) {
            *ok = false;
        }
        lastError = expr + " is not a normal number";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    return res.toNumber();
}
Example #2
0
/**
 * Evaluates the given mathematical expression and returns the result.
 * Angles may be expressed in degrees (default), rad (#r), gon (#g)
 * or as surveyors angles (N#d#'#"E).
 */
double RMath::eval(const QString& expression, bool* ok) {
    lastError = "";

    if (expression.isEmpty()) {
        if (ok!=NULL) {
            *ok = false;
        }
        lastError = "Expression is empty";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    if (ok!=NULL) {
        *ok = true;
    }

    QString expr = expression;

    // 'correct' commas in numbers to points:
    if (RSettings::getNumberLocale().decimalPoint()==',') {
        expr.replace(QRegExp("(\\d*),(\\d+)"), "\\1.\\2");
    }

    int idx = -1;

    // convert surveyor type angles (e.g. N10d30'12.5"E) to degrees:
    if (expr.contains(QRegExp("[NESW]", Qt::CaseInsensitive))) {
        // \b(?:(?:([NS])(?:([+-]?)(?:(?:(\d*\.?\d*)[d°])?(?:(\d*\.?\d*)')?(?:(\d*\.?\d*)")?|(\d*))([EW]))?)|([EW]))\b
        QRegExp re(
            "\\b"                               // a word
            "(?:"
              "(?:"
                "([NS])"                        // expression starts with nord or south
                "(?:"
                  "([+-]?)"                     // sign
                  "(?:"
                    "(?:(\\d*\\.?\\d*)[d°])?"   // degrees with d
                    "(?:(\\d*\\.?\\d*)')?"      // minutes with '
                    "(?:(\\d*\\.?\\d*)\")?"     // seconds with "
                    "|"                         // or...
                    "(\\d*)"                    // degrees without d
                  ")"
                  "([EW])"                      // east or west
                ")?"
              ")"
              "|"                               // or...
              "([EW])"                          // only east (0d) or west (180d)
            ")"
            "\\b",
            Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            double angle = 0.0;
            QString sign;

            // "E" or "W":
            if (!re.cap(8).isEmpty()) {
                if (re.cap(8).toUpper()=="E") {
                    angle = 0.0;
                }
                else if (re.cap(8).toUpper()=="W") {
                    angle = 180.0;
                }
                else {
                    if (ok!=NULL) {
                        *ok = false;
                    }
                    lastError = "Invalid cardinal direction found (valid values are: N,E,S,W)";
                    return RNANDOUBLE;
                }
            }
            // "[NS]...[EW]":
            else {
                bool north = re.cap(1).toUpper()=="N";
                bool south = re.cap(1).toUpper()=="S";
                sign = re.cap(2);
                double degrees = 0.0;
                double minutes = 0.0;
                double seconds = 0.0;
                if (!re.cap(6).isEmpty()) {
                    degrees = re.cap(6).toDouble(ok);
                }
                else {
                    degrees = re.cap(3).toDouble(ok);
                    minutes = re.cap(4).toDouble(ok);
                    seconds = re.cap(5).toDouble(ok);
                }
                bool east = re.cap(7).toUpper()=="E";
                bool west = re.cap(7).toUpper()=="W";

                double base = (north ? 90.0 : 270.0);
                int dir = ((north && west) || (south && east) ? 1 : -1);
                angle = base + dir * (degrees + minutes/60.0 + seconds/3600.0);
            }

            expr.replace(
                re.cap(),
                QString("%1%2").arg(sign).arg(angle, 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 001 is: " << expr;

    // convert radiant angles (e.g. "1.2r") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))r\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            //qDebug() << "RMath::eval: match 001a is: " << match;
            expr.replace(
                re,
                QString("%1").arg(rad2deg(match.toDouble(ok)), 0, 'g', 16)
            );
            //qDebug() << "RMath::eval: expression 001a is: " << expr;
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 002 is: " << expr;

    // convert grad angles (e.g. "100g") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))g\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            expr.replace(
                re,
                QString("%1").arg(gra2deg(match.toDouble(ok)), 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression 003 is: " << expr;

    // convert explicitely indicated degree angles (e.g. "90d") to degrees:
    {
        QRegExp re("((?:\\.\\d+)|(?:\\d+\\.\\d*)|(?:\\d+))d\\b", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString match = re.cap(1);
            expr.replace(
                re,
                QString("%1").arg(match.toDouble(ok), 0, 'g', 16)
            );
        } while(idx!=-1);
    }

    // convert fraction notation to formula:
    // e.g. 7 3/32 to 7+3/32
    {
        QRegExp re("(\\d+)[ ]+(\\d+/\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString numberString = re.cap(1);
            QString fractionString = re.cap(2);
            expr.replace(
                re,
                QString("%1+%2").arg(numberString).arg(fractionString)
            );
        } while(idx!=-1);
    }

    // convert foot indication to a factor of 12
    // e.g. "10'" to 10*12 and "10' " to 10*12+
    {
        QRegExp re("(\\d+)'[ ]*(\\d*)", Qt::CaseInsensitive, QRegExp::RegExp2);
        do {
            idx = re.indexIn(expr);
            if (idx==-1) {
                break;
            }
            QString feetString = re.cap(1);
            //QString spacesString = re.cap(2);
            QString inchString = re.cap(2);
            expr.replace(
                re,
                QString("%1*12%2%3")
                    .arg(feetString)
                    .arg(inchString.isEmpty() ? "" : "+")
                    .arg(inchString)
            );
        } while(idx!=-1);
    }

    //qDebug() << "RMath::eval: expression is: " << expr;

    QScriptEngine e;


    if (mathExt.isNull()) {
        QString inputJs = "scripts/input.js";
        QFile file(inputJs);
        if (file.exists()) {
            if (file.open(QIODevice::ReadOnly|QIODevice::Text)) {
                mathExt = QString(file.readAll());
                file.close();
            }
        }
        else {
            qDebug() << "file not found: input.js";
        }
    }
    e.evaluate(mathExt);

    QScriptValue res = e.evaluate(expr);

    if (res.isError()) {
        if (ok!=NULL) {
            *ok = false;
        }
        lastError = res.toString();
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    if (!res.isNumber()) {
        if (ok != NULL) {
            *ok = false;
        }
        lastError = expr + " is not a number";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    //qDebug() << "res.toNumber(): " << res.toNumber();
    //qDebug() << "fpclassify: " << std::fpclassify(res.toNumber());

    if (!isNormal(res.toNumber())) {
        if (ok != NULL) {
            *ok = false;
        }
        lastError = expr + " is not a normal number";
        //qDebug() << "RMath::evel: error: " << lastError;
        return RNANDOUBLE;
    }

    return res.toNumber();
}