From 43cd5d0e35df926eeaacec3f8d676bb5f6dd287b Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 12 Oct 2018 16:59:29 -0400 Subject: SERVER-32930 Add trigonometric expressions to aggregation Closes #1287 Signed-off-by: Charlie Swanson --- src/mongo/db/pipeline/expression_trigonometric.cpp | 220 +++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/mongo/db/pipeline/expression_trigonometric.cpp (limited to 'src/mongo/db/pipeline/expression_trigonometric.cpp') diff --git a/src/mongo/db/pipeline/expression_trigonometric.cpp b/src/mongo/db/pipeline/expression_trigonometric.cpp new file mode 100644 index 00000000000..6fdf7cdcc4e --- /dev/null +++ b/src/mongo/db/pipeline/expression_trigonometric.cpp @@ -0,0 +1,220 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "expression_trigonometric.h" + +namespace mongo { + +/* ----------------------- Inclusive Bounded Trigonometric Functions ---------------------------- */ + +#define CREATE_BOUNDED_TRIGONOMETRIC_CLASS(className, funcName, boundType, lowerBound, upperBound) \ + class Expression##className final \ + : public ExpressionBoundedTrigonometric { \ + public: \ + explicit Expression##className(const boost::intrusive_ptr& expCtx) \ + : ExpressionBoundedTrigonometric(expCtx) {} \ + double getLowerBound() const final { \ + return lowerBound; \ + } \ + \ + double getUpperBound() const final { \ + return upperBound; \ + } \ + \ + double doubleFunc(double arg) const final { \ + return std::funcName(arg); \ + } \ + \ + Decimal128 decimalFunc(Decimal128 arg) const final { \ + return arg.funcName(); \ + } \ + \ + const char* getOpName() const final { \ + return "$" #funcName; \ + } \ + }; \ + REGISTER_EXPRESSION(funcName, Expression##className::parse); + + +/** + * Inclusive Bounds + */ +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(ArcCosine, acos, InclusiveBoundType, -1.0, 1.0); + +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(ArcSine, asin, InclusiveBoundType, -1.0, 1.0); + +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(HyperbolicArcTangent, atanh, InclusiveBoundType, -1.0, 1.0); + +CREATE_BOUNDED_TRIGONOMETRIC_CLASS( + HyperbolicArcCosine, acosh, InclusiveBoundType, 1.0, std::numeric_limits::infinity()); + +/** + * Exclusive Bounds + */ +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(Cosine, + cos, + ExclusiveBoundType, + -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); + +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(Sine, + sin, + ExclusiveBoundType, + -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); + +CREATE_BOUNDED_TRIGONOMETRIC_CLASS(Tangent, + tan, + ExclusiveBoundType, + -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); + +#undef CREATE_BOUNDED_TRIGONOMETRIC_CLASS + +/* ----------------------- Unbounded Trigonometric Functions ---------------------------- */ + + +#define CREATE_TRIGONOMETRIC_CLASS(className, funcName) \ + class Expression##className final \ + : public ExpressionUnboundedTrigonometric { \ + public: \ + explicit Expression##className(const boost::intrusive_ptr& expCtx) \ + : ExpressionUnboundedTrigonometric(expCtx) {} \ + \ + double doubleFunc(double arg) const final { \ + return std::funcName(arg); \ + } \ + \ + Decimal128 decimalFunc(Decimal128 arg) const final { \ + return arg.funcName(); \ + } \ + \ + const char* getOpName() const final { \ + return "$" #funcName; \ + } \ + }; \ + REGISTER_EXPRESSION(funcName, Expression##className::parse); + +CREATE_TRIGONOMETRIC_CLASS(ArcTangent, atan); +CREATE_TRIGONOMETRIC_CLASS(HyperbolicArcSine, asinh); +CREATE_TRIGONOMETRIC_CLASS(HyperbolicCosine, cosh); +CREATE_TRIGONOMETRIC_CLASS(HyperbolicSine, sinh); +CREATE_TRIGONOMETRIC_CLASS(HyperbolicTangent, tanh); + +#undef CREATE_TRIGONOMETRIC_CLASS + + +/* ----------------------- ExpressionArcTangent2 ---------------------------- */ + +class ExpressionArcTangent2 final : public ExpressionTwoNumericArgs { +public: + explicit ExpressionArcTangent2(const boost::intrusive_ptr& expCtx) + : ExpressionTwoNumericArgs(expCtx) {} + + Value evaluateNumericArgs(const Value& numericArg1, const Value& numericArg2) const final { + auto totalType = BSONType::NumberDouble; + // If the type of either argument is NumberDecimal, we promote to Decimal128. + if (numericArg1.getType() == BSONType::NumberDecimal || + numericArg2.getType() == BSONType::NumberDecimal) { + totalType = BSONType::NumberDecimal; + } + switch (totalType) { + case BSONType::NumberDecimal: { + auto dec = numericArg1.coerceToDecimal(); + return Value(dec.atan2(numericArg2.coerceToDecimal())); + } + case BSONType::NumberDouble: { + return Value( + std::atan2(numericArg1.coerceToDouble(), numericArg2.coerceToDouble())); + } + default: + MONGO_UNREACHABLE; + } + } + + const char* getOpName() const final { + return "$atan2"; + } +}; +REGISTER_EXPRESSION(atan2, ExpressionArcTangent2::parse); + + +/* ----------------------- ExpressionDegreesToRadians and ExpressionRadiansToDegrees ---- */ + +static constexpr double kDoublePi = 3.141592653589793; +static constexpr double kDoublePiOver180 = kDoublePi / 180.0; +static constexpr double kDouble180OverPi = 180.0 / kDoublePi; + +static Value doDegreeRadiansConversion(const Value& numericArg, + Decimal128 decimalFactor, + double doubleFactor) { + switch (numericArg.getType()) { + case BSONType::NumberDecimal: + return Value(numericArg.getDecimal().multiply(decimalFactor)); + default: + return Value(numericArg.coerceToDouble() * doubleFactor); + } +} + +class ExpressionDegreesToRadians final + : public ExpressionSingleNumericArg { +public: + explicit ExpressionDegreesToRadians(const boost::intrusive_ptr& expCtx) + : ExpressionSingleNumericArg(expCtx) {} + + Value evaluateNumericArg(const Value& numericArg) const final { + return doDegreeRadiansConversion(numericArg, Decimal128::kPiOver180, kDoublePiOver180); + } + + const char* getOpName() const final { + return "$degreesToRadians"; + } +}; + +REGISTER_EXPRESSION(degreesToRadians, ExpressionDegreesToRadians::parse); + +class ExpressionRadiansToDegrees final + : public ExpressionSingleNumericArg { +public: + explicit ExpressionRadiansToDegrees(const boost::intrusive_ptr& expCtx) + : ExpressionSingleNumericArg(expCtx) {} + + Value evaluateNumericArg(const Value& numericArg) const final { + return doDegreeRadiansConversion(numericArg, Decimal128::k180OverPi, kDouble180OverPi); + } + + const char* getOpName() const final { + return "$radiansToDegrees"; + } +}; + +REGISTER_EXPRESSION(radiansToDegrees, ExpressionRadiansToDegrees::parse); +} // namespace mongo -- cgit v1.2.1