/** * 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(); }
/** * 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(); }