/** * Copyright (C) 2019-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/db/exec/sbe/vm/vm.h" #include "mongo/db/exec/sbe/accumulator_sum_value_enum.h" #include "mongo/db/exec/sbe/values/arith_common.h" #include "mongo/db/query/collation/collator_interface.h" #include "mongo/platform/overflow_arithmetic.h" #include "mongo/util/represent_as.h" #include "mongo/util/summation.h" #include "mongo/util/time_support.h" namespace mongo { namespace sbe { namespace vm { using namespace value; namespace { static constexpr double kDoublePi = 3.141592653589793; static constexpr double kDoublePiOver180 = kDoublePi / 180.0; static constexpr double kDouble180OverPi = 180.0 / kDoublePi; // Structures defining trigonometric functions computation. struct Acos { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.acos(); } else { result = std::acos(arg); } } }; struct Acosh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.acosh(); } else { result = std::acosh(arg); } } }; struct Asin { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.asin(); } else { result = std::asin(arg); } } }; struct Asinh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.asinh(); } else { result = std::asinh(arg); } } }; struct Atan { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.atan(); } else { result = std::atan(arg); } } }; struct Atanh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.atanh(); } else { result = std::atanh(arg); } } }; struct Cos { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.cos(); } else { result = std::cos(arg); } } }; struct Cosh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.cosh(); } else { result = std::cosh(arg); } } }; struct Sin { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.sin(); } else { result = std::sin(arg); } } }; struct Sinh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.sinh(); } else { result = std::sinh(arg); } } }; struct Tan { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.tan(); } else { result = std::tan(arg); } } }; struct Tanh { template static void computeFunction(const ArgT& arg, ResT& result) { if constexpr (std::is_same_v) { result = arg.tanh(); } else { result = std::tanh(arg); } } }; /** * Template for generic trigonometric function. The type in the template is a structure defining the * computation of the respective trigonometric function. */ template FastTuple genericTrigonometricFun(value::TypeTags argTag, value::Value argValue) { if (value::isNumber(argTag)) { switch (argTag) { case value::TypeTags::NumberInt32: { double result; TrigFunction::computeFunction(numericCast(argTag, argValue), result); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberInt64: { double result; TrigFunction::computeFunction(numericCast(argTag, argValue), result); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDouble: { double result; TrigFunction::computeFunction(numericCast(argTag, argValue), result); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { Decimal128 result; TrigFunction::computeFunction(numericCast(argTag, argValue), result); auto [resTag, resValue] = value::makeCopyDecimal(result); return {true, resTag, resValue}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } } // namespace namespace { void setNonDecimalTotal(TypeTags nonDecimalTotalTag, const DoubleDoubleSummation& nonDecimalTotal, Array* arr) { auto [sum, addend] = nonDecimalTotal.getDoubleDouble(); arr->setAt(AggSumValueElems::kNonDecimalTotalTag, nonDecimalTotalTag, value::bitcastFrom(0.0)); arr->setAt(AggSumValueElems::kNonDecimalTotalSum, TypeTags::NumberDouble, value::bitcastFrom(sum)); arr->setAt(AggSumValueElems::kNonDecimalTotalAddend, TypeTags::NumberDouble, value::bitcastFrom(addend)); } void setDecimalTotal(TypeTags nonDecimalTotalTag, const DoubleDoubleSummation& nonDecimalTotal, const Decimal128& decimalTotal, Array* arr) { setNonDecimalTotal(nonDecimalTotalTag, nonDecimalTotal, arr); // We don't need to use 'ValueGuard' for decimal because we've already allocated enough storage // and Array::push_back() is guaranteed to not throw. auto [tag, val] = makeCopyDecimal(decimalTotal); if (arr->size() < AggSumValueElems::kMaxSizeOfArray) { arr->push_back(tag, val); } else { arr->setAt(AggSumValueElems::kDecimalTotal, tag, val); } } void addNonDecimal(TypeTags tag, Value val, DoubleDoubleSummation& nonDecimalTotal) { switch (tag) { case TypeTags::NumberInt64: nonDecimalTotal.addLong(value::bitcastTo(val)); break; case TypeTags::NumberInt32: nonDecimalTotal.addInt(value::bitcastTo(val)); break; case TypeTags::NumberDouble: nonDecimalTotal.addDouble(value::bitcastTo(val)); break; default: MONGO_UNREACHABLE_TASSERT(5755316); } } void setStdDevArray(value::Value count, value::Value mean, value::Value m2, Array* arr) { arr->setAt(AggStdDevValueElems::kCount, value::TypeTags::NumberInt64, count); arr->setAt(AggStdDevValueElems::kRunningMean, value::TypeTags::NumberDouble, mean); arr->setAt(AggStdDevValueElems::kRunningM2, value::TypeTags::NumberDouble, m2); } } // namespace void ByteCode::aggDoubleDoubleSumImpl(value::Array* arr, value::TypeTags rhsTag, value::Value rhsValue) { if (!isNumber(rhsTag)) { return; } tassert(5755310, str::stream() << "The result slot must have at least " << AggSumValueElems::kMaxSizeOfArray - 1 << " elements but got: " << arr->size(), arr->size() >= AggSumValueElems::kMaxSizeOfArray - 1); // Only uses tag information from the kNonDecimalTotalTag element. auto [nonDecimalTotalTag, _] = arr->getAt(AggSumValueElems::kNonDecimalTotalTag); tassert(5755311, "The nonDecimalTag can't be NumberDecimal", nonDecimalTotalTag != TypeTags::NumberDecimal); // Only uses values from the kNonDecimalTotalSum/kNonDecimalTotalAddend elements. auto [sumTag, sum] = arr->getAt(AggSumValueElems::kNonDecimalTotalSum); auto [addendTag, addend] = arr->getAt(AggSumValueElems::kNonDecimalTotalAddend); tassert(5755312, "The sum and addend must be NumberDouble", sumTag == addendTag && sumTag == TypeTags::NumberDouble); // We're guaranteed to always have a valid nonDecimalTotal value. auto nonDecimalTotal = DoubleDoubleSummation::create(value::bitcastTo(sum), value::bitcastTo(addend)); if (auto nElems = arr->size(); nElems < AggSumValueElems::kMaxSizeOfArray) { // We haven't seen any decimal value so far. if (auto totalTag = getWidestNumericalType(nonDecimalTotalTag, rhsTag); totalTag == TypeTags::NumberDecimal) { // We have seen a decimal for the first time and start storing the total sum // of decimal values into 'kDecimalTotal' element and the total sum of non-decimal // values into 'kNonDecimalXXX' elements. tassert( 5755313, "The arg value must be NumberDecimal", rhsTag == TypeTags::NumberDecimal); setDecimalTotal( nonDecimalTotalTag, nonDecimalTotal, value::bitcastTo(rhsValue), arr); } else { addNonDecimal(rhsTag, rhsValue, nonDecimalTotal); setNonDecimalTotal(totalTag, nonDecimalTotal, arr); } } else { // We've seen a decimal value. We've already started storing the total sum of decimal values // into 'kDecimalTotal' element and the total sum of non-decimal values into // 'kNonDecimalXXX' elements. tassert(5755314, str::stream() << "The result slot must have at most " << AggSumValueElems::kMaxSizeOfArray << " elements but got: " << arr->size(), nElems == AggSumValueElems::kMaxSizeOfArray); auto [decimalTotalTag, decimalTotalVal] = arr->getAt(AggSumValueElems::kDecimalTotal); tassert(5755315, "The decimalTotal must be NumberDecimal", decimalTotalTag == TypeTags::NumberDecimal); auto decimalTotal = value::bitcastTo(decimalTotalVal); if (rhsTag == TypeTags::NumberDecimal) { decimalTotal = decimalTotal.add(value::bitcastTo(rhsValue)); } else { nonDecimalTotalTag = getWidestNumericalType(nonDecimalTotalTag, rhsTag); addNonDecimal(rhsTag, rhsValue, nonDecimalTotal); } setDecimalTotal(nonDecimalTotalTag, nonDecimalTotal, decimalTotal, arr); } } void ByteCode::aggMergeDoubleDoubleSumsImpl(value::Array* accumulator, value::TypeTags rhsTag, value::Value rhsValue) { auto [accumWidestType, _1] = accumulator->getAt(AggSumValueElems::kNonDecimalTotalTag); tassert(7039532, "value must be of type 'Array'", rhsTag == value::TypeTags::Array); auto nextDoubleDoubleArr = value::getArrayView(rhsValue); tassert(7039533, "array does not have enough elements", nextDoubleDoubleArr->size() >= AggSumValueElems::kMaxSizeOfArray - 1); // First aggregate the non-decimal sum, then the non-decimal addend. Both should be doubles. auto [sumTag, sum] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalSum); tassert(7039534, "expected 'NumberDouble'", sumTag == value::TypeTags::NumberDouble); aggDoubleDoubleSumImpl(accumulator, sumTag, sum); auto [addendTag, addend] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalAddend); tassert(7039535, "expected 'NumberDouble'", addendTag == value::TypeTags::NumberDouble); // There is a special case when the 'sum' is infinite and the 'addend' is NaN. This DoubleDouble // value represents infinity, not NaN. Therefore, we avoid incorporating the NaN 'addend' value // into the sum. if (std::isfinite(value::bitcastTo(sum)) || !std::isnan(value::bitcastTo(addend))) { aggDoubleDoubleSumImpl(accumulator, addendTag, addend); } // Determine the widest non-decimal type that we've seen so far, and set the accumulator state // accordingly. We do this after computing the sums, since 'aggDoubleDoubleSumImpl()' will // set the widest type to 'NumberDouble' when we call it above. auto [newValWidestType, _2] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalTag); tassert( 7039536, "unexpected 'NumberDecimal'", newValWidestType != value::TypeTags::NumberDecimal); tassert( 7039537, "unexpected 'NumberDecimal'", accumWidestType != value::TypeTags::NumberDecimal); auto widestType = getWidestNumericalType(newValWidestType, accumWidestType); accumulator->setAt( AggSumValueElems::kNonDecimalTotalTag, widestType, value::bitcastFrom(0)); // If there's a decimal128 sum as part of the incoming DoubleDouble sum, incorporate it into the // accumulator. if (nextDoubleDoubleArr->size() == AggSumValueElems::kMaxSizeOfArray) { auto [decimalTotalTag, decimalTotalVal] = nextDoubleDoubleArr->getAt(AggSumValueElems::kDecimalTotal); tassert(7039538, "The decimalTotal must be 'NumberDecimal'", decimalTotalTag == TypeTags::NumberDecimal); aggDoubleDoubleSumImpl(accumulator, decimalTotalTag, decimalTotalVal); } } void ByteCode::aggStdDevImpl(value::Array* arr, value::TypeTags rhsTag, value::Value rhsValue) { if (!isNumber(rhsTag)) { return; } auto [countTag, countVal] = arr->getAt(AggStdDevValueElems::kCount); tassert(5755201, "The count must be of type NumberInt64", countTag == TypeTags::NumberInt64); auto [meanTag, meanVal] = arr->getAt(AggStdDevValueElems::kRunningMean); auto [m2Tag, m2Val] = arr->getAt(AggStdDevValueElems::kRunningM2); tassert(5755202, "The mean and m2 must be of type Double", m2Tag == meanTag && meanTag == TypeTags::NumberDouble); double inputDouble = 0.0; // Within our query execution engine, $stdDevPop and $stdDevSamp do not maintain the precision // of decimal types and converts all values to double. We do this here by converting // NumberDecimal to Decimal128 and then extract a double value from it. if (rhsTag == value::TypeTags::NumberDecimal) { auto decimal = value::bitcastTo(rhsValue); inputDouble = decimal.toDouble(); } else { inputDouble = numericCast(rhsTag, rhsValue); } auto curVal = value::bitcastFrom(inputDouble); auto count = value::bitcastTo(countVal); tassert(5755211, "The total number of elements must be less than INT64_MAX", ++count < std::numeric_limits::max()); auto newCountVal = value::bitcastFrom(count); auto [deltaOwned, deltaTag, deltaVal] = genericSub(value::TypeTags::NumberDouble, curVal, value::TypeTags::NumberDouble, meanVal); auto [deltaDivCountOwned, deltaDivCountTag, deltaDivCountVal] = genericDiv(deltaTag, deltaVal, value::TypeTags::NumberInt64, newCountVal); auto [newMeanOwned, newMeanTag, newMeanVal] = genericAdd(meanTag, meanVal, deltaDivCountTag, deltaDivCountVal); auto [newDeltaOwned, newDeltaTag, newDeltaVal] = genericSub(value::TypeTags::NumberDouble, curVal, newMeanTag, newMeanVal); auto [deltaMultNewDeltaOwned, deltaMultNewDeltaTag, deltaMultNewDeltaVal] = genericMul(deltaTag, deltaVal, newDeltaTag, newDeltaVal); auto [newM2Owned, newM2Tag, newM2Val] = genericAdd(m2Tag, m2Val, deltaMultNewDeltaTag, deltaMultNewDeltaVal); return setStdDevArray(newCountVal, newMeanVal, newM2Val, arr); } void ByteCode::aggMergeStdDevsImpl(value::Array* accumulator, value::TypeTags rhsTag, value::Value rhsValue) { tassert(7039542, "expected value of type 'Array'", rhsTag == value::TypeTags::Array); auto nextArr = value::getArrayView(rhsValue); tassert(7039543, "expected array to have exactly 3 elements", accumulator->size() == AggStdDevValueElems::kSizeOfArray); tassert(7039544, "expected array to have exactly 3 elements", nextArr->size() == AggStdDevValueElems::kSizeOfArray); auto [newCountTag, newCountVal] = nextArr->getAt(AggStdDevValueElems::kCount); tassert(7039545, "expected 64-bit int", newCountTag == value::TypeTags::NumberInt64); int64_t newCount = value::bitcastTo(newCountVal); // If the incoming partial aggregate has a count of zero, then it represents the partial // standard deviation of no data points. This means that it can be safely ignored, and we return // the accumulator as is. if (newCount == 0) { return; } auto [oldCountTag, oldCountVal] = accumulator->getAt(AggStdDevValueElems::kCount); tassert(7039546, "expected 64-bit int", oldCountTag == value::TypeTags::NumberInt64); int64_t oldCount = value::bitcastTo(oldCountVal); auto [oldMeanTag, oldMeanVal] = accumulator->getAt(AggStdDevValueElems::kRunningMean); tassert(7039547, "expected double", oldMeanTag == value::TypeTags::NumberDouble); double oldMean = value::bitcastTo(oldMeanVal); auto [newMeanTag, newMeanVal] = nextArr->getAt(AggStdDevValueElems::kRunningMean); tassert(7039548, "expected double", newMeanTag == value::TypeTags::NumberDouble); double newMean = value::bitcastTo(newMeanVal); auto [oldM2Tag, oldM2Val] = accumulator->getAt(AggStdDevValueElems::kRunningM2); tassert(7039531, "expected double", oldM2Tag == value::TypeTags::NumberDouble); double oldM2 = value::bitcastTo(oldM2Val); auto [newM2Tag, newM2Val] = nextArr->getAt(AggStdDevValueElems::kRunningM2); tassert(7039541, "expected double", newM2Tag == value::TypeTags::NumberDouble); double newM2 = value::bitcastTo(newM2Val); const double delta = newMean - oldMean; // We've already handled the case where 'newCount' is zero above. This means that 'totalCount' // must be positive, and prevents us from ever dividing by zero in the subsequent calculation. int64_t totalCount = oldCount + newCount; if (delta != 0) { newMean = ((oldCount * oldMean) + (newCount * newMean)) / totalCount; newM2 += delta * delta * (static_cast(oldCount) * static_cast(newCount) / totalCount); } newM2 += oldM2; setStdDevArray(value::bitcastFrom(totalCount), value::bitcastFrom(newMean), value::bitcastFrom(newM2), accumulator); } FastTuple ByteCode::aggStdDevFinalizeImpl( value::Value fieldValue, bool isSamp) { auto arr = value::getArrayView(fieldValue); auto [countTag, countVal] = arr->getAt(AggStdDevValueElems::kCount); tassert(5755207, "The count must be a NumberInt64", countTag == value::TypeTags::NumberInt64); auto count = value::bitcastTo(countVal); if (count == 0) { return {true, value::TypeTags::Null, 0}; } if (isSamp && count == 1) { return {true, value::TypeTags::Null, 0}; } auto [m2Tag, m2] = arr->getAt(AggStdDevValueElems::kRunningM2); tassert(5755208, "The m2 value must be of type NumberDouble", m2Tag == value::TypeTags::NumberDouble); auto m2Double = value::bitcastTo(m2); auto variance = isSamp ? (m2Double / (count - 1)) : (m2Double / count); auto stdDev = sqrt(variance); return {true, value::TypeTags::NumberDouble, value::bitcastFrom(stdDev)}; } FastTuple ByteCode::genericDiv(value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags rhsTag, value::Value rhsValue) { auto assertNonZero = [](bool nonZero) { uassert(4848401, "can't $divide by zero", nonZero); }; if (value::isNumber(lhsTag) && value::isNumber(rhsTag)) { switch (getWidestNumericalType(lhsTag, rhsTag)) { case value::TypeTags::NumberInt32: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = numericCast(lhsTag, lhsValue) / numericCast(rhsTag, rhsValue); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberInt64: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = numericCast(lhsTag, lhsValue) / numericCast(rhsTag, rhsValue); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDouble: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = numericCast(lhsTag, lhsValue) / numericCast(rhsTag, rhsValue); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { assertNonZero(!numericCast(rhsTag, rhsValue).isZero()); auto result = numericCast(lhsTag, lhsValue) .divide(numericCast(rhsTag, rhsValue)); auto [tag, val] = value::makeCopyDecimal(result); return {true, tag, val}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericIDiv(value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags rhsTag, value::Value rhsValue) { auto assertNonZero = [](bool nonZero) { uassert(4848402, "can't $divide by zero", nonZero); }; if (value::isNumber(lhsTag) && value::isNumber(rhsTag)) { switch (getWidestNumericalType(lhsTag, rhsTag)) { case value::TypeTags::NumberInt32: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = numericCast(lhsTag, lhsValue) / numericCast(rhsTag, rhsValue); return {false, value::TypeTags::NumberInt32, value::bitcastFrom(result)}; } case value::TypeTags::NumberInt64: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = numericCast(lhsTag, lhsValue) / numericCast(rhsTag, rhsValue); return {false, value::TypeTags::NumberInt64, value::bitcastFrom(result)}; } case value::TypeTags::NumberDouble: { auto lhs = representAs(numericCast(lhsTag, lhsValue)); auto rhs = representAs(numericCast(rhsTag, rhsValue)); if (!lhs || !rhs) { return {false, value::TypeTags::Nothing, 0}; } assertNonZero(*rhs != 0); auto result = *lhs / *rhs; return {false, value::TypeTags::NumberInt64, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto lhs = representAs(numericCast(lhsTag, lhsValue)); auto rhs = representAs(numericCast(rhsTag, rhsValue)); if (!lhs || !rhs) { return {false, value::TypeTags::Nothing, 0}; } assertNonZero(*rhs != 0); auto result = *lhs / *rhs; return {false, value::TypeTags::NumberInt64, value::bitcastFrom(result)}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericMod(value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags rhsTag, value::Value rhsValue) { auto assertNonZero = [](bool nonZero) { uassert(4848403, "can't $mod by zero", nonZero); }; if (value::isNumber(lhsTag) && value::isNumber(rhsTag)) { switch (getWidestNumericalType(lhsTag, rhsTag)) { case value::TypeTags::NumberInt32: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = overflow::safeMod(numericCast(lhsTag, lhsValue), numericCast(rhsTag, rhsValue)); return {false, value::TypeTags::NumberInt32, value::bitcastFrom(result)}; } case value::TypeTags::NumberInt64: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = overflow::safeMod(numericCast(lhsTag, lhsValue), numericCast(rhsTag, rhsValue)); return {false, value::TypeTags::NumberInt64, value::bitcastFrom(result)}; } case value::TypeTags::NumberDouble: { assertNonZero(numericCast(rhsTag, rhsValue) != 0); auto result = fmod(numericCast(lhsTag, lhsValue), numericCast(rhsTag, rhsValue)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { assertNonZero(!numericCast(rhsTag, rhsValue).isZero()); auto result = numericCast(lhsTag, lhsValue) .modulo(numericCast(rhsTag, rhsValue)); auto [tag, val] = value::makeCopyDecimal(result); return {true, tag, val}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericAbs(value::TypeTags operandTag, value::Value operandValue) { switch (operandTag) { case value::TypeTags::NumberInt32: { auto operand = value::bitcastTo(operandValue); if (operand == std::numeric_limits::min()) { return {false, value::TypeTags::NumberInt64, value::bitcastFrom(-int64_t{operand})}; } return {false, value::TypeTags::NumberInt32, value::bitcastFrom(operand >= 0 ? operand : -operand)}; } case value::TypeTags::NumberInt64: { auto operand = value::bitcastTo(operandValue); if (operand == std::numeric_limits::min()) { // Absolute value of the minimum int64_t value does not fit in any integer type. return {false, value::TypeTags::Nothing, 0}; } return {false, value::TypeTags::NumberInt64, value::bitcastFrom(operand >= 0 ? operand : -operand)}; } case value::TypeTags::NumberDouble: { auto operand = value::bitcastTo(operandValue); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(operand >= 0 ? operand : -operand)}; } case value::TypeTags::NumberDecimal: { auto operand = value::bitcastTo(operandValue); auto [tag, value] = value::makeCopyDecimal(operand.toAbs()); return {true, tag, value}; } default: return {false, value::TypeTags::Nothing, 0}; } } FastTuple ByteCode::genericCeil(value::TypeTags operandTag, value::Value operandValue) { if (isNumber(operandTag)) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto result = std::ceil(value::bitcastTo(operandValue)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = value::bitcastTo(operandValue) .quantize(Decimal128::kNormalizedZero, Decimal128::kRoundTowardPositive); auto [tag, value] = value::makeCopyDecimal(result); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: // Ceil on integer values is the identity function. return {false, operandTag, operandValue}; default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericFloor(value::TypeTags operandTag, value::Value operandValue) { if (isNumber(operandTag)) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto result = std::floor(value::bitcastTo(operandValue)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = value::bitcastTo(operandValue) .quantize(Decimal128::kNormalizedZero, Decimal128::kRoundTowardNegative); auto [tag, value] = value::makeCopyDecimal(result); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: // Floor on integer values is the identity function. return {false, operandTag, operandValue}; default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericExp(value::TypeTags operandTag, value::Value operandValue) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto result = exp(value::bitcastTo(operandValue)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = value::bitcastTo(operandValue).exponential(); auto [tag, value] = value::makeCopyDecimal(result); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: { auto operand = (operandTag == value::TypeTags::NumberInt32) ? static_cast(value::bitcastTo(operandValue)) : static_cast(value::bitcastTo(operandValue)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(exp(operand))}; } default: return {false, value::TypeTags::Nothing, 0}; } } FastTuple ByteCode::genericLn(value::TypeTags operandTag, value::Value operandValue) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto operand = value::bitcastTo(operandValue); if (operand <= 0 && !std::isnan(operand)) { // Logarithms are only defined on the domain of positive numbers and NaN. NaN is a // legal input to ln(), returning NaN. return {false, value::TypeTags::Nothing, 0}; } // Note: NaN is a legal input to log(), returning NaN. return {false, value::TypeTags::NumberDouble, value::bitcastFrom(std::log(operand))}; } case value::TypeTags::NumberDecimal: { auto operand = value::bitcastTo(operandValue); if (!operand.isGreater(Decimal128::kNormalizedZero) && !operand.isNaN()) { return {false, value::TypeTags::Nothing, 0}; } auto operandLn = operand.logarithm(); auto [tag, value] = value::makeCopyDecimal(operandLn); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: { auto operand = (operandTag == value::TypeTags::NumberInt32) ? static_cast(value::bitcastTo(operandValue)) : static_cast(value::bitcastTo(operandValue)); if (operand <= 0 && !std::isnan(operand)) { return {false, value::TypeTags::Nothing, 0}; } return {false, value::TypeTags::NumberDouble, value::bitcastFrom(std::log(operand))}; } default: return {false, value::TypeTags::Nothing, 0}; } } FastTuple ByteCode::genericLog10(value::TypeTags operandTag, value::Value operandValue) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto operand = value::bitcastTo(operandValue); if (operand <= 0 && !std::isnan(operand)) { // Logarithms are only defined on the domain of positive numbers and NaN. NaN is a // legal input to log10(), returning NaN. return {false, value::TypeTags::Nothing, 0}; } return {false, value::TypeTags::NumberDouble, value::bitcastFrom(std::log10(operand))}; } case value::TypeTags::NumberDecimal: { auto operand = value::bitcastTo(operandValue); if (!operand.isGreater(Decimal128::kNormalizedZero) && !operand.isNaN()) { return {false, value::TypeTags::Nothing, 0}; } auto operandLog10 = operand.logarithm(Decimal128(10)); auto [tag, value] = value::makeCopyDecimal(operandLog10); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: { auto operand = (operandTag == value::TypeTags::NumberInt32) ? static_cast(value::bitcastTo(operandValue)) : static_cast(value::bitcastTo(operandValue)); if (operand <= 0 && !std::isnan(operand)) { return {false, value::TypeTags::Nothing, 0}; } return {false, value::TypeTags::NumberDouble, value::bitcastFrom(std::log10(operand))}; } default: return {false, value::TypeTags::Nothing, 0}; } } FastTuple ByteCode::genericSqrt(value::TypeTags operandTag, value::Value operandValue) { switch (operandTag) { case value::TypeTags::NumberDouble: { auto operand = value::bitcastTo(operandValue); if (operand < 0 && !std::isnan(operand)) { // Sqrt is only defined in the domain of non-negative numbers and NaN. NaN is a // legal input to sqrt(), returning NaN. return {false, value::TypeTags::Nothing, 0}; } // Note: NaN is a legal input to sqrt(), returning NaN. return { false, value::TypeTags::NumberDouble, value::bitcastFrom(sqrt(operand))}; } case value::TypeTags::NumberDecimal: { auto operand = value::bitcastTo(operandValue); if (operand.isLess(Decimal128::kNormalizedZero) && !operand.isNaN()) { return {false, value::TypeTags::Nothing, 0}; } auto [tag, value] = value::makeCopyDecimal(operand.squareRoot()); return {true, tag, value}; } case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: { auto operand = (operandTag == value::TypeTags::NumberInt32) ? static_cast(value::bitcastTo(operandValue)) : static_cast(value::bitcastTo(operandValue)); if (operand < 0 && !std::isnan(operand)) { return {false, value::TypeTags::Nothing, 0}; } return { false, value::TypeTags::NumberDouble, value::bitcastFrom(sqrt(operand))}; } default: return {false, value::TypeTags::Nothing, 0}; } } std::pair ByteCode::genericNot(value::TypeTags tag, value::Value value) { if (tag == value::TypeTags::Boolean) { return {tag, value::bitcastFrom(!value::bitcastTo(value))}; } else { return {value::TypeTags::Nothing, 0}; } } std::pair ByteCode::compare3way( value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags rhsTag, value::Value rhsValue, const StringData::ComparatorInterface* comparator) { if (lhsTag == value::TypeTags::Nothing || rhsTag == value::TypeTags::Nothing) { return {value::TypeTags::Nothing, 0}; } return value::compareValue(lhsTag, lhsValue, rhsTag, rhsValue, comparator); } std::pair ByteCode::compare3way(value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags rhsTag, value::Value rhsValue, value::TypeTags collTag, value::Value collValue) { if (collTag != value::TypeTags::collator) { return {value::TypeTags::Nothing, 0}; } auto comparator = static_cast(getCollatorView(collValue)); return value::compareValue(lhsTag, lhsValue, rhsTag, rhsValue, comparator); } FastTuple ByteCode::genericAcos(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAcosh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAsin(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAsinh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAtan(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAtanh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericAtan2(value::TypeTags argTag1, value::Value argValue1, value::TypeTags argTag2, value::Value argValue2) { if (value::isNumber(argTag1) && value::isNumber(argTag2)) { switch (getWidestNumericalType(argTag1, argTag2)) { case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: case value::TypeTags::NumberDouble: { auto result = std::atan2(numericCast(argTag1, argValue1), numericCast(argTag2, argValue2)); return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = numericCast(argTag1, argValue1) .atan2(numericCast(argTag2, argValue2)); auto [resTag, resValue] = value::makeCopyDecimal(result); return {true, resTag, resValue}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericCos(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericCosh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericDegreesToRadians( value::TypeTags argTag, value::Value argValue) { if (value::isNumber(argTag)) { switch (argTag) { case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: case value::TypeTags::NumberDouble: { auto result = numericCast(argTag, argValue) * kDoublePiOver180; return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = numericCast(argTag, argValue).multiply(Decimal128::kPiOver180); auto [resTag, resValue] = value::makeCopyDecimal(result); return {true, resTag, resValue}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericRadiansToDegrees( value::TypeTags argTag, value::Value argValue) { if (value::isNumber(argTag)) { switch (argTag) { case value::TypeTags::NumberInt32: case value::TypeTags::NumberInt64: case value::TypeTags::NumberDouble: { auto result = numericCast(argTag, argValue) * kDouble180OverPi; return {false, value::TypeTags::NumberDouble, value::bitcastFrom(result)}; } case value::TypeTags::NumberDecimal: { auto result = numericCast(argTag, argValue).multiply(Decimal128::k180OverPi); auto [resTag, resValue] = value::makeCopyDecimal(result); return {true, resTag, resValue}; } default: MONGO_UNREACHABLE; } } return {false, value::TypeTags::Nothing, 0}; } FastTuple ByteCode::genericSin(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericSinh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericTan(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } FastTuple ByteCode::genericTanh(value::TypeTags argTag, value::Value argValue) { return genericTrigonometricFun(argTag, argValue); } } // namespace vm } // namespace sbe } // namespace mongo