summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDrew Paroski <drew.paroski@mongodb.com>2021-04-29 00:01:01 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-09-25 19:49:50 +0000
commit0f2414b2a4b1b3babd07e0153569fa4fd30aabe2 (patch)
treef4e365bf2270a3ceb5ea703710698f853f0150f6
parent6862ef35ecbd1399305e924a8165289f2fc0b180 (diff)
downloadmongo-0f2414b2a4b1b3babd07e0153569fa4fd30aabe2.tar.gz
SERVER-56326 Add a round() method to the Decimal128 class
(cherry picked from commit cdfe92c4bcf1a4ff9f43cab56c587bca74ca2ffc) (cherry picked from commit 0c74ca79e7ab0385040f835ab7d3eb6d2d3063ee) (cherry picked from commit b5e1fb13cc2c67d48877e30bffae57a9c29b7b62)
-rw-r--r--src/mongo/platform/decimal128.cpp26
-rw-r--r--src/mongo/platform/decimal128.h7
-rw-r--r--src/mongo/platform/decimal128_test.cpp165
3 files changed, 198 insertions, 0 deletions
diff --git a/src/mongo/platform/decimal128.cpp b/src/mongo/platform/decimal128.cpp
index 73d315acd9b..7110d92d249 100644
--- a/src/mongo/platform/decimal128.cpp
+++ b/src/mongo/platform/decimal128.cpp
@@ -322,6 +322,32 @@ Decimal128 Decimal128::toAbs() const {
return Decimal128(libraryTypeToValue(dec128));
}
+Decimal128 Decimal128::round(RoundingMode roundMode) const {
+ std::uint32_t throwAwayFlag = 0;
+ return round(&throwAwayFlag, roundMode);
+}
+
+Decimal128 Decimal128::round(std::uint32_t* signalingFlags, RoundingMode roundMode) const {
+ BID_UINT128 current = decimal128ToLibraryType(_value);
+ current = [&]() {
+ switch (roundMode) {
+ case kRoundTiesToEven:
+ return bid128_round_integral_nearest_even(current, signalingFlags);
+ case kRoundTowardNegative:
+ return bid128_round_integral_negative(current, signalingFlags);
+ case kRoundTowardPositive:
+ return bid128_round_integral_positive(current, signalingFlags);
+ case kRoundTowardZero:
+ return bid128_round_integral_zero(current, signalingFlags);
+ case kRoundTiesToAway:
+ return bid128_round_integral_nearest_away(current, signalingFlags);
+ }
+ MONGO_UNREACHABLE;
+ }();
+
+ return Decimal128{libraryTypeToValue(current)};
+}
+
std::int32_t Decimal128::toInt(RoundingMode roundMode) const {
std::uint32_t throwAwayFlag = 0;
return toInt(&throwAwayFlag, roundMode);
diff --git a/src/mongo/platform/decimal128.h b/src/mongo/platform/decimal128.h
index 5a1508317aa..6d4b42f31e8 100644
--- a/src/mongo/platform/decimal128.h
+++ b/src/mongo/platform/decimal128.h
@@ -249,6 +249,9 @@ public:
return Decimal128(negated);
}
+ Decimal128 round(RoundingMode roundMode = kRoundTiesToEven) const;
+ Decimal128 round(std::uint32_t* signalingFlags,
+ RoundingMode roundMode = kRoundTiesToEven) const;
/**
* This set of functions converts a Decimal128 to a certain integer type with a
@@ -427,6 +430,10 @@ public:
return _value.high64 == other._value.high64 && _value.low64 == other._value.low64;
}
+ Decimal128 operator-() const {
+ return negate();
+ }
+
private:
static const uint8_t kSignFieldPos = 64 - 1;
static const uint8_t kCombinationFieldPos = kSignFieldPos - 17;
diff --git a/src/mongo/platform/decimal128_test.cpp b/src/mongo/platform/decimal128_test.cpp
index bb38254d0ff..27d213ff9e4 100644
--- a/src/mongo/platform/decimal128_test.cpp
+++ b/src/mongo/platform/decimal128_test.cpp
@@ -1075,6 +1075,171 @@ TEST(Decimal128Test, TestDecimal128Quantize) {
ASSERT_EQUALS(result.getValue().high64, expected.getValue().high64);
}
+struct RoundingModeInfo {
+ Decimal128::RoundingMode mode;
+ StringData name;
+};
+
+static constexpr std::array<RoundingModeInfo, 5> roundingModes{
+ {{Decimal128::kRoundTiesToEven, "kRoundTiesToEven"_sd},
+ {Decimal128::kRoundTowardNegative, "kRoundTowardNegative"_sd},
+ {Decimal128::kRoundTowardPositive, "kRoundTowardPositive"_sd},
+ {Decimal128::kRoundTowardZero, "kRoundTowardZero"_sd},
+ {Decimal128::kRoundTiesToAway, "kRoundTiesToAway"_sd}}};
+
+std::string convertUint64ToString(uint64_t value) {
+ constexpr int hexStrLength = sizeof(uint64_t) * 2;
+ char buffer[hexStrLength + 1];
+ int n = snprintf(buffer, sizeof(buffer), "%016llX", static_cast<unsigned long long>(value));
+ invariant(n >= 0);
+ return std::string(buffer);
+}
+
+void assertRoundingTestCase(const Decimal128& actual,
+ const Decimal128& expected,
+ StringData roundingModeName,
+ int testCaseLineNumber) {
+ auto const& actualVal = actual.getValue();
+ auto const& expectedVal = expected.getValue();
+
+ if (actualVal.low64 != expectedVal.low64 || actualVal.high64 != expectedVal.high64) {
+ FAIL(str::stream() << "Rounding test case defined on line " << testCaseLineNumber
+ << " failed. Rounding mode: "
+ << roundingModeName
+ << ". "
+ << "Expected: {"
+ << convertUint64ToString(expectedVal.low64)
+ << ", "
+ << convertUint64ToString(expectedVal.high64)
+ << "}. Actual: {"
+ << convertUint64ToString(actualVal.low64)
+ << ", "
+ << convertUint64ToString(actualVal.high64)
+ << "}");
+ }
+}
+
+TEST(Decimal128Test, TestDecimal128RoundingFractionalValues) {
+ auto d = [](auto x) { return Decimal128{x}; };
+
+ // 'pBig' is the largest positive integer value where Decimal128 can represent pBig + 0.1
+ // without losing precision.
+ auto pBig = d(std::string(33, '9'));
+ auto nBig = -pBig;
+
+ auto pBigPlus1 = pBig.add(d(1));
+ auto nBigMinus1 = nBig.add(d(-1));
+
+ // 'epsilon' is the smallest positive value where Decimal128 can represent 1.0 + epsilon
+ // without losing precision.
+ auto epsilon = d("1E-33");
+
+ struct TestCase {
+ int lineNumber;
+ Decimal128 val;
+ Decimal128 expected[5];
+ };
+
+ std::array<TestCase, 36> cases = {{
+ {__LINE__, d(0.5).add(d("-1E-34")), {d(0), d(0), d(1), d(0), d(0)}},
+ {__LINE__, d(0.5), {d(0), d(0), d(1), d(0), d(1)}},
+ {__LINE__, d(0.5).add(d("1E-34")), {d(1), d(0), d(1), d(0), d(1)}},
+ {__LINE__, d(1.5).add(-epsilon), {d(1), d(1), d(2), d(1), d(1)}},
+ {__LINE__, d(1.5), {d(2), d(1), d(2), d(1), d(2)}},
+ {__LINE__, d(1.5).add(epsilon), {d(2), d(1), d(2), d(1), d(2)}},
+ {__LINE__, d(2.5).add(-epsilon), {d(2), d(2), d(3), d(2), d(2)}},
+ {__LINE__, d(2.5), {d(2), d(2), d(3), d(2), d(3)}},
+ {__LINE__, d(2.5).add(epsilon), {d(3), d(2), d(3), d(2), d(3)}},
+ {__LINE__, d(3.5).add(-epsilon), {d(3), d(3), d(4), d(3), d(3)}},
+ {__LINE__, d(3.5), {d(4), d(3), d(4), d(3), d(4)}},
+ {__LINE__, d(3.5).add(epsilon), {d(4), d(3), d(4), d(3), d(4)}},
+ {__LINE__, d(9.5).add(-epsilon), {d(9), d(9), d(10), d(9), d(9)}},
+ {__LINE__, d(9.5), {d(10), d(9), d(10), d(9), d(10)}},
+ {__LINE__, d(9.5).add(epsilon), {d(10), d(9), d(10), d(9), d(10)}},
+ {__LINE__, d(-0.5).add(d("1E-34")), {d(-0.0), d(-1), d(-0.0), d(-0.0), d(-0.0)}},
+ {__LINE__, d(-0.5), {d(-0.0), d(-1), d(-0.0), d(-0.0), d(-1)}},
+ {__LINE__, d(-0.5).add(d("-1E-34")), {d(-1), d(-1), d(-0.0), d(-0.0), d(-1)}},
+ {__LINE__, d(-1.5).add(epsilon), {d(-1), d(-2), d(-1), d(-1), d(-1)}},
+ {__LINE__, d(-1.5), {d(-2), d(-2), d(-1), d(-1), d(-2)}},
+ {__LINE__, d(-1.5).add(-epsilon), {d(-2), d(-2), d(-1), d(-1), d(-2)}},
+ {__LINE__, d(-2.5).add(epsilon), {d(-2), d(-3), d(-2), d(-2), d(-2)}},
+ {__LINE__, d(-2.5), {d(-2), d(-3), d(-2), d(-2), d(-3)}},
+ {__LINE__, d(-2.5).add(-epsilon), {d(-3), d(-3), d(-2), d(-2), d(-3)}},
+ {__LINE__, d(-3.5).add(epsilon), {d(-3), d(-4), d(-3), d(-3), d(-3)}},
+ {__LINE__, d(-3.5), {d(-4), d(-4), d(-3), d(-3), d(-4)}},
+ {__LINE__, d(-3.5).add(-epsilon), {d(-4), d(-4), d(-3), d(-3), d(-4)}},
+ {__LINE__, d(-9.5).add(epsilon), {d(-9), d(-10), d(-9), d(-9), d(-9)}},
+ {__LINE__, d(-9.5), {d(-10), d(-10), d(-9), d(-9), d(-10)}},
+ {__LINE__, d(-9.5).add(-epsilon), {d(-10), d(-10), d(-9), d(-9), d(-10)}},
+ {__LINE__, pBig.add(d("0.4")), {pBig, pBig, pBigPlus1, pBig, pBig}},
+ {__LINE__, pBig.add(d("0.5")), {pBigPlus1, pBig, pBigPlus1, pBig, pBigPlus1}},
+ {__LINE__, pBig.add(d("0.6")), {pBigPlus1, pBig, pBigPlus1, pBig, pBigPlus1}},
+ {__LINE__, nBig.add(d("-0.4")), {nBig, nBigMinus1, nBig, nBig, nBig}},
+ {__LINE__, nBig.add(d("-0.5")), {nBigMinus1, nBigMinus1, nBig, nBig, nBigMinus1}},
+ {__LINE__, nBig.add(d("-0.6")), {nBigMinus1, nBigMinus1, nBig, nBig, nBigMinus1}},
+ }};
+
+ for (auto testCase : cases) {
+ for (size_t i = 0; i < roundingModes.size(); ++i) {
+ auto actual = testCase.val.round(roundingModes[i].mode);
+ assertRoundingTestCase(
+ actual, testCase.expected[i], roundingModes[i].name, testCase.lineNumber);
+ }
+ }
+}
+
+TEST(Decimal128Test, TestDecimal128RoundingIntegerValues) {
+ struct TestCase {
+ int lineNumber;
+ Decimal128 val;
+ };
+
+ std::array<TestCase, 16> cases = {{{__LINE__, Decimal128{"0"}},
+ {__LINE__, Decimal128{"1"}},
+ {__LINE__, Decimal128{"2"}},
+ {__LINE__, Decimal128{"3"}},
+ {__LINE__, Decimal128{"4"}},
+ {__LINE__, Decimal128{DBL_MAX}},
+ {__LINE__, Decimal128{std::string(34, '9')}},
+ {__LINE__, Decimal128{"-0"}},
+ {__LINE__, Decimal128{"-1"}},
+ {__LINE__, Decimal128{"-2"}},
+ {__LINE__, Decimal128{"-3"}},
+ {__LINE__, Decimal128{"-4"}},
+ {__LINE__, Decimal128{-DBL_MAX}},
+ {__LINE__, Decimal128{"-" + std::string(34, '9')}},
+ {__LINE__, Decimal128::kLargestPositive},
+ {__LINE__, Decimal128::kLargestNegative}}};
+
+ for (auto testCase : cases) {
+ for (size_t i = 0; i < roundingModes.size(); ++i) {
+ auto actual = testCase.val.round(roundingModes[i].mode);
+ auto const& expected = testCase.val;
+ assertRoundingTestCase(actual, expected, roundingModes[i].name, testCase.lineNumber);
+ }
+ }
+}
+
+TEST(Decimal128Test, TestDecimal128RoundingInfinityAndNan) {
+ struct TestCase {
+ int lineNumber;
+ Decimal128 val;
+ };
+
+ std::array<TestCase, 4> cases = {{{__LINE__, Decimal128::kPositiveInfinity},
+ {__LINE__, Decimal128::kNegativeInfinity},
+ {__LINE__, Decimal128::kPositiveNaN},
+ {__LINE__, Decimal128::kNegativeNaN}}};
+
+ for (auto testCase : cases) {
+ for (size_t i = 0; i < roundingModes.size(); ++i) {
+ auto actual = testCase.val.round(roundingModes[i].mode);
+ auto const& expected = testCase.val;
+ assertRoundingTestCase(actual, expected, roundingModes[i].name, testCase.lineNumber);
+ }
+ }
+}
+
TEST(Decimal128Test, TestDecimal128NormalizeSmallVals) {
Decimal128 d1("500E-2");
Decimal128 d2("5");