From e039271638d6dba34e5b64834e3f12e87aeb6455 Mon Sep 17 00:00:00 2001 From: Billy Donahue Date: Fri, 14 Jun 2019 13:13:21 -0400 Subject: SERVER-24374 Make Decimal128 integer ctors constexpr reverts fab9fde6ba which reverted 668100df79 --- src/mongo/platform/decimal128.cpp | 31 +++----- src/mongo/platform/decimal128.h | 99 +++++++++++++++++--------- src/mongo/platform/decimal128_test.cpp | 125 +++++++++++++++------------------ 3 files changed, 133 insertions(+), 122 deletions(-) (limited to 'src/mongo/platform') diff --git a/src/mongo/platform/decimal128.cpp b/src/mongo/platform/decimal128.cpp index 18d59af7490..a48a310bf74 100644 --- a/src/mongo/platform/decimal128.cpp +++ b/src/mongo/platform/decimal128.cpp @@ -187,15 +187,6 @@ BID_UINT128 decimal128ToLibraryType(Decimal128::Value value) { } } // namespace -Decimal128::Decimal128(std::int32_t int32Value) - : _value(libraryTypeToValue(bid128_from_int32(int32Value))) {} - -Decimal128::Decimal128(std::int64_t int64Value) - : _value(libraryTypeToValue(bid128_from_int64(int64Value))) {} - -Decimal128::Decimal128(std::uint64_t uint64Value) - : _value(libraryTypeToValue(bid128_from_uint64(uint64Value))) {} - /** * Quantize a doubleValue argument to a Decimal128 with exactly 15 digits * of precision. @@ -292,8 +283,8 @@ Decimal128::Decimal128(double doubleValue, // Check if the quantization was done correctly: _value stores exactly 15 // decimal digits of precision (15 digits can fit into the low 64 bits of the decimal) - uint64_t kSmallest15DigitInt = 1E14; // A 1 with 14 zeros - uint64_t kLargest15DigitInt = 1E15 - 1; // 15 nines + std::uint64_t kSmallest15DigitInt = 1E14; // A 1 with 14 zeros + std::uint64_t kLargest15DigitInt = 1E15 - 1; // 15 nines if (getCoefficientLow() > kLargest15DigitInt) { // If we didn't precisely get 15 digits of precision, the original base 10 exponent // guess was 1 off, so quantize once more with base10Exp + 1 @@ -324,10 +315,6 @@ Decimal128::Decimal128(std::string stringValue, _value = libraryTypeToValue(dec128); } -Decimal128::Value Decimal128::getValue() const { - return _value; -} - Decimal128 Decimal128::toAbs() const { BID_UINT128 dec128 = decimal128ToLibraryType(_value); dec128 = bid128_abs(dec128); @@ -461,12 +448,12 @@ std::int32_t Decimal128::toInt(std::uint32_t* signalingFlags, RoundingMode round } } -int64_t Decimal128::toLong(RoundingMode roundMode) const { +std::int64_t Decimal128::toLong(RoundingMode roundMode) const { std::uint32_t throwAwayFlag = 0; return toLong(&throwAwayFlag, roundMode); } -int64_t Decimal128::toLong(std::uint32_t* signalingFlags, RoundingMode roundMode) const { +std::int64_t Decimal128::toLong(std::uint32_t* signalingFlags, RoundingMode roundMode) const { BID_UINT128 dec128 = decimal128ToLibraryType(_value); switch (roundMode) { case kRoundTiesToEven: @@ -913,10 +900,10 @@ Decimal128 Decimal128::quantize(const Decimal128& reference, auto normalizedThis = this->normalize(); auto normalizedReferenceExponent = - static_cast(reference.normalize().getBiasedExponent()); + static_cast(reference.normalize().getBiasedExponent()); if (normalizedReferenceExponent != 0 && - (static_cast(normalizedThis.getBiasedExponent()) - normalizedReferenceExponent) > - 33) { + (static_cast(normalizedThis.getBiasedExponent()) - + normalizedReferenceExponent) > 33) { return normalizedThis; } return nonNormalizingQuantize(reference, signalingFlags, roundMode); @@ -989,7 +976,7 @@ const std::uint64_t t17 = 100ull * 1000 * 1000 * 1000 * 1000 * 1000; // Computed by running the calculations in Python, and verified with static_assert. const std::uint64_t t34lo64 = 4003012203950112767ULL; #if defined(__GNUC__) -static_assert(t34lo64 == t17 * t17 - 1, "precomputed constant is wrong"); +MONGO_STATIC_ASSERT(t34lo64 == t17 * t17 - 1); #endif // Mod t17 by 2^32 to get the low 32 bits of t17's binary representation const std::uint64_t t17lo32 = t17 % (1ull << 32); @@ -1014,7 +1001,7 @@ const Decimal128 Decimal128::kSmallestNegative(1, 0, 0, 1); // Get the representation of 0 (0E0). const Decimal128 Decimal128::kNormalizedZero(Decimal128::Value( - {0, static_cast(Decimal128::kExponentBias) << Decimal128::kExponentFieldPos})); + {0, static_cast(Decimal128::kExponentBias) << Decimal128::kExponentFieldPos})); // Shift the format of the combination bits to the right position to get Inf and NaN // +Inf = 0111 1000 ... ... = 0x78 ... ..., -Inf = 1111 1000 ... ... = 0xf8 ... ... diff --git a/src/mongo/platform/decimal128.h b/src/mongo/platform/decimal128.h index 7b96d8e2611..3295e28a2b9 100644 --- a/src/mongo/platform/decimal128.h +++ b/src/mongo/platform/decimal128.h @@ -33,11 +33,13 @@ #include #include #include +#include #include #include "mongo/config.h" #include "mongo/util/assert_util.h" +#include "mongo/util/if_constexpr.h" namespace mongo { @@ -75,10 +77,11 @@ public: static const Decimal128 kPiOver180; static const Decimal128 k180OverPi; - static const uint32_t kMaxBiasedExponent = 6143 + 6144; + static constexpr std::uint32_t kMaxBiasedExponent = 6143 + 6144; // Biased exponent of a Decimal128 with least significant digit in the units place - static const int32_t kExponentBias = 6143 + 33; - static const uint32_t kInfinityExponent = kMaxBiasedExponent + 1; // internal convention only + static constexpr std::int32_t kExponentBias = 6143 + 33; + static constexpr std::uint32_t kInfinityExponent = + kMaxBiasedExponent + 1; // internal convention only /** * This struct holds the raw data for IEEE 754-2008 data types @@ -124,17 +127,17 @@ public: kInexact = 0x20, }; - static bool hasFlag(std::uint32_t signalingFlags, SignalingFlag f) { + constexpr static bool hasFlag(std::uint32_t signalingFlags, SignalingFlag f) { return ((signalingFlags & f) != 0u); } /** * Returns true if a valid Decimal can be constructed from the given arguments. */ - static bool isValid(uint64_t sign, - uint64_t exponent, - uint64_t coefficientHigh, - uint64_t coefficientLow) { + constexpr static bool isValid(std::uint64_t sign, + std::uint64_t exponent, + std::uint64_t coefficientHigh, + std::uint64_t coefficientLow) { if (coefficientHigh >= 0x1ed09bead87c0 && (coefficientHigh != 0x1ed09bead87c0 || coefficientLow != 0x378d8e63ffffffff)) { return false; @@ -149,7 +152,10 @@ public: /** * Construct a 0E0 valued Decimal128. */ - Decimal128() : _value(kNormalizedZero._value) {} + constexpr Decimal128() + : _value{0, + static_cast(Decimal128::kExponentBias) + << Decimal128::kExponentFieldPos} {} /** * This constructor takes in a raw decimal128 type, which consists of two @@ -162,16 +168,15 @@ public: * Constructs a Decimal128 from parts, dealing with proper encoding of the combination field. * Assumes that the value will be inside the valid range of finite values. (No NaN/Inf, etc.) */ - Decimal128(uint64_t sign, uint64_t exponent, uint64_t coefficientHigh, uint64_t coefficientLow) - : _value( - Value{coefficientLow, - (sign << kSignFieldPos) | (exponent << kExponentFieldPos) | coefficientHigh}) { - dassert(isValid(sign, exponent, coefficientHigh, coefficientLow)); - } + constexpr Decimal128(std::uint64_t sign, + std::uint64_t exponent, + std::uint64_t coefficientHigh, + std::uint64_t coefficientLow) + : _value(_valueFromParts(sign, exponent, coefficientHigh, coefficientLow)) {} - explicit Decimal128(std::int32_t int32Value); - explicit Decimal128(std::int64_t int64Value); - explicit Decimal128(std::uint64_t uint64Value); + template >> + constexpr explicit Decimal128(T v) + : Decimal128(v < 0 ? 1 : 0, kExponentBias, 0, _makeCoefficientLow(v)) {} /** * This constructor takes a double and constructs a Decimal128 object given a roundMode, either @@ -207,13 +212,15 @@ public: /** * This function gets the inner Value struct storing a Decimal128 value. */ - Value getValue() const; + constexpr Value getValue() const { + return _value; + } /** * Extracts the biased exponent from the combination field. */ - uint32_t getBiasedExponent() const { - const uint64_t combo = _getCombinationField(); + constexpr std::uint32_t getBiasedExponent() const { + const std::uint64_t combo = _getCombinationField(); if (combo < kCombinationNonCanonical) return combo >> 3; @@ -226,7 +233,7 @@ public: * Returns the high 49 bits of the 113-bit binary encoded coefficient. Returns 0 for * non-canonical or non-finite numbers. */ - uint64_t getCoefficientHigh() const { + constexpr std::uint64_t getCoefficientHigh() const { return _getCombinationField() < kCombinationNonCanonical ? _value.high64 & kCanonicalCoefficientHighFieldMask : 0; @@ -236,7 +243,7 @@ public: * Returns the low 64 bits of the 113-bit binary encoded coefficient. Returns 0 for * non-canonical or non-finite numbers. */ - uint64_t getCoefficientLow() const { + constexpr std::uint64_t getCoefficientLow() const { return _getCombinationField() < kCombinationNonCanonical ? _value.low64 : 0; } @@ -519,15 +526,16 @@ public: } private: - static const uint8_t kSignFieldPos = 64 - 1; - static const uint8_t kCombinationFieldPos = kSignFieldPos - 17; - static const uint64_t kCombinationFieldMask = (1 << 17) - 1; - static const uint64_t kExponentFieldPos = kCombinationFieldPos + 3; - static const uint64_t kCoefficientContinuationFieldMask = (1ull << kCombinationFieldPos) - 1; - static const uint64_t kCombinationNonCanonical = 3 << 15; - static const uint64_t kCombinationInfinity = 0x1e << 12; - static const uint64_t kCombinationNaN = 0x1f << 12; - static const uint64_t kCanonicalCoefficientHighFieldMask = (1ull << 49) - 1; + constexpr static std::uint8_t kSignFieldPos = 64 - 1; + constexpr static std::uint8_t kCombinationFieldPos = kSignFieldPos - 17; + constexpr static std::uint64_t kCombinationFieldMask = (1 << 17) - 1; + constexpr static std::uint64_t kExponentFieldPos = kCombinationFieldPos + 3; + constexpr static std::uint64_t kCoefficientContinuationFieldMask = + (1ull << kCombinationFieldPos) - 1; + constexpr static std::uint64_t kCombinationNonCanonical = 3 << 15; + constexpr static std::uint64_t kCombinationInfinity = 0x1e << 12; + constexpr static std::uint64_t kCombinationNaN = 0x1f << 12; + constexpr static std::uint64_t kCanonicalCoefficientHighFieldMask = (1ull << 49) - 1; std::string _convertToScientificNotation(StringData coefficient, int adjustedExponent) const; std::string _convertToStandardDecimalNotation(StringData coefficient, int exponent) const; @@ -542,10 +550,35 @@ private: std::uint32_t* signalingFlags, RoundingMode roundMode = kRoundTiesToEven) const; - uint64_t _getCombinationField() const { + constexpr std::uint64_t _getCombinationField() const { return (_value.high64 >> kCombinationFieldPos) & kCombinationFieldMask; } + constexpr static Value _valueFromParts(std::uint64_t sign, + std::uint64_t exponent, + std::uint64_t coefficientHigh, + std::uint64_t coefficientLow) { + // For constexpr's sake the invariant must be compiled only if !isValid(). + if (!isValid(sign, exponent, coefficientHigh, coefficientLow)) { + invariant(false, "invalid arguments to Decimal128()"); + } + + return Value{coefficientLow, + (sign << kSignFieldPos) | (exponent << kExponentFieldPos) | coefficientHigh}; + } + + // Constructs the absolute value of v as a uint64_t in a constexpr-permitted way. + template + constexpr std::uint64_t _makeCoefficientLow(T i) { + // if constexpr to avoid MSVC warnings. + IF_CONSTEXPR(std::is_signed_v) { + return i < 0 ? static_cast(-i) : static_cast(i); + } + else { + return static_cast(i); + } + } + Value _value; }; } // namespace mongo diff --git a/src/mongo/platform/decimal128_test.cpp b/src/mongo/platform/decimal128_test.cpp index 1f0362e6a3a..4881c369216 100644 --- a/src/mongo/platform/decimal128_test.cpp +++ b/src/mongo/platform/decimal128_test.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -40,80 +41,68 @@ namespace mongo { +using namespace fmt::literals; + // Tests for Decimal128 constructors TEST(Decimal128Test, TestDefaultConstructor) { Decimal128 d; ASSERT_TRUE(d.isBinaryEqual(Decimal128(0))); } -TEST(Decimal128Test, TestInt32ConstructorZero) { - int32_t intZero = 0; - Decimal128 d(intZero); - Decimal128::Value val = d.getValue(); - // 0x3040000000000000 0000000000000000 = +0E+0 - uint64_t highBytes = 0x3040000000000000ull; - uint64_t lowBytes = 0x0000000000000000ull; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); -} +template +using Lim = std::numeric_limits; -TEST(Decimal128Test, TestInt32ConstructorMax) { - int32_t intMax = std::numeric_limits::max(); - Decimal128 d(intMax); - Decimal128::Value val = d.getValue(); - // 0x3040000000000000 000000007fffffff = +2147483647E+0 - uint64_t highBytes = 0x3040000000000000ull; - uint64_t lowBytes = 0x000000007fffffffull; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); -} +TEST(Decimal128Test, TestConstructor) { + // High bits of a positive Decimal128 with exponent 1. + constexpr uint64_t posHigh = 0x3040000000000000; + // High bits of a negative Decimal128 with exponent 1. + constexpr uint64_t negHigh = 0xb040000000000000; -TEST(Decimal128Test, TestInt32ConstructorMin) { - int32_t intMin = std::numeric_limits::lowest(); - Decimal128 d(intMin); - Decimal128::Value val = d.getValue(); - // 0xb040000000000000 000000007fffffff = -2147483648E+0 - uint64_t highBytes = 0xb040000000000000ull; - uint64_t lowBytes = 0x0000000080000000ull; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); -} +#define TEST_CTOR(N, HIGH64, LOW64) \ + ASSERT_EQ(Decimal128{N}.getValue().high64, static_cast(HIGH64)); \ + ASSERT_EQ(Decimal128{N}.getValue().low64, static_cast(LOW64)); -TEST(Decimal128Test, TestInt64ConstructorZero) { - int64_t longZero = 0; - Decimal128 d(longZero); - Decimal128::Value val = d.getValue(); - // 0x3040000000000000 0000000000000000 = +0E+0 - uint64_t highBytes = 0x3040000000000000ull; - uint64_t lowBytes = 0x0000000000000000ull; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); -} + TEST_CTOR(int8_t{0}, posHigh, 0); // +0E+0 + TEST_CTOR(Lim::lowest(), negHigh, uint64_t{1} << 7); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 7) - 1); + TEST_CTOR(int8_t{5}, posHigh, 0x5); -TEST(Decimal128Test, TestInt64ConstructorMax) { - int64_t longMax = std::numeric_limits::max(); - Decimal128 d(longMax); - Decimal128::Value val = d.getValue(); - // 0x3040000000000000 7fffffffffffffff = +9223372036854775807E+0 - uint64_t highBytes = 0x3040000000000000ull; - uint64_t lowBytes = 0x7fffffffffffffffull; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); -} + TEST_CTOR(uint8_t{0}, posHigh, 0); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 8) - 1); + TEST_CTOR(uint8_t{5}, posHigh, 0x5); -TEST(Decimal128Test, TestInt64ConstructorMin) { - int64_t longMin = std::numeric_limits::lowest(); - Decimal128 d(longMin); - Decimal128::Value val = d.getValue(); - // 0xb040000000000000 8000000000000000 = -9223372036854775808E+0 - uint64_t highBytes = 0xb040000000000000; - uint64_t lowBytes = 0x8000000000000000; - ASSERT_EQUALS(val.high64, highBytes); - ASSERT_EQUALS(val.low64, lowBytes); + TEST_CTOR(int16_t{0}, posHigh, 0); + TEST_CTOR(Lim::lowest(), negHigh, uint64_t{1} << 15); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 15) - 1); + TEST_CTOR(int16_t{5}, posHigh, 0x5); + + TEST_CTOR(uint16_t{0}, posHigh, 0); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 16) - 1); + TEST_CTOR(uint16_t{5}, posHigh, 0x5); + + TEST_CTOR(int32_t{0}, posHigh, 0); + TEST_CTOR(Lim::lowest(), negHigh, uint64_t{1} << 31); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 31) - 1); + TEST_CTOR(int32_t{5}, posHigh, 0x5); + + TEST_CTOR(uint32_t{0}, posHigh, 0); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 32) - 1); + TEST_CTOR(uint32_t{5}, posHigh, 0x5); + + TEST_CTOR(int64_t{0}, posHigh, 0); + TEST_CTOR(Lim::lowest(), negHigh, uint64_t{1} << 63); + TEST_CTOR(Lim::max(), posHigh, (uint64_t{1} << 63) - 1); + TEST_CTOR(int64_t{5}, posHigh, 0x5); + + TEST_CTOR(uint64_t{0}, posHigh, 0); + TEST_CTOR(Lim::max(), posHigh, Lim::max()); + TEST_CTOR(uint64_t{5}, posHigh, 0x5); + +#undef TEST_CTOR } TEST(Decimal128Test, TestPartsConstructor) { - Decimal128 expected(10); + constexpr Decimal128 expected(10); Decimal128 val(0LL, Decimal128::kExponentBias, 0LL, 10LL); ASSERT_EQUALS(val.getValue().low64, expected.getValue().low64); ASSERT_EQUALS(val.getValue().low64, expected.getValue().low64); @@ -298,17 +287,19 @@ TEST(Decimal128Test, TestNonCanonicalDecimal) { // when encountered. However, the exponent and sign still matter. // 0x6c10000000000000 0000000000000000 = non-canonical 0, all ignored bits clear - Decimal128 nonCanonical0E0(Decimal128::Value{0, 0x6c10000000000000ull}); + constexpr Decimal128 nonCanonical0E0(Decimal128::Value{0, 0x6c10000000000000ull}); std::string zeroE0 = nonCanonical0E0.toString(); ASSERT_EQUALS(zeroE0, "0"); // 0xec100000deadbeef 0123456789abcdef = non-canonical -0, random stuff in ignored bits - Decimal128 nonCanonicalM0E0(Decimal128::Value{0x0123456789abcdefull, 0xec100000deadbeefull}); + constexpr Decimal128 nonCanonicalM0E0( + Decimal128::Value{0x0123456789abcdefull, 0xec100000deadbeefull}); std::string minusZeroE0 = nonCanonicalM0E0.toString(); ASSERT_EQUALS(minusZeroE0, "-0"); // 0x6c11fffffffffffff ffffffffffffffff = non-canonical 0.000, all ignored bits set - Decimal128 nonCanonical0E3(Decimal128::Value{0xffffffffffffffffull, 0x6c11ffffffffffffull}); + constexpr Decimal128 nonCanonical0E3( + Decimal128::Value{0xffffffffffffffffull, 0x6c11ffffffffffffull}); std::string zeroE3 = nonCanonical0E3.toString(); ASSERT_EQUALS(zeroE3, "0E+3"); @@ -327,13 +318,13 @@ TEST(Decimal128Test, TestNonCanonicalDecimal) { // Tests for absolute value function TEST(Decimal128Test, TestAbsValuePos) { - Decimal128 d(25); + constexpr Decimal128 d(25); Decimal128 dAbs = d.toAbs(); ASSERT_TRUE(dAbs.isEqual(d)); } TEST(Decimal128Test, TestAbsValueNeg) { - Decimal128 d(-25); + constexpr Decimal128 d(-25); Decimal128 dAbs = d.toAbs(); ASSERT_TRUE(dAbs.isEqual(Decimal128(25))); } @@ -958,8 +949,8 @@ TEST(Decimal128Test, TestDecimal128DivideSignaling) { // Test Decimal128 special comparisons TEST(Decimal128Test, TestDecimal128IsZero) { - Decimal128 d1(0); - Decimal128 d2(500); + constexpr Decimal128 d1(0); + constexpr Decimal128 d2(500); ASSERT_TRUE(d1.isZero()); ASSERT_FALSE(d2.isZero()); } -- cgit v1.2.1