diff options
author | Billy Donahue <billy.donahue@mongodb.com> | 2019-06-17 15:43:05 -0400 |
---|---|---|
committer | Billy Donahue <billy.donahue@mongodb.com> | 2019-06-27 17:03:26 -0400 |
commit | 644fc54e18049e0737ae1621aec5eb7afbe1934f (patch) | |
tree | 4ea4c21b4ee88ff9b46e891a122e8e249691fae4 /src/mongo/platform | |
parent | fd8538074a35666a8f18f54ffae798095cbc43f9 (diff) | |
download | mongo-644fc54e18049e0737ae1621aec5eb7afbe1934f.tar.gz |
SERVER-24374 Make Decimal128 integer ctors constexpr
- avoid signed overflow and integer promotion
- no std::signbit, avoid cast warnings in abs
- restore libbase->intelfp dependency back to public LIDEP
- volatile test
Diffstat (limited to 'src/mongo/platform')
-rw-r--r-- | src/mongo/platform/decimal128.cpp | 31 | ||||
-rw-r--r-- | src/mongo/platform/decimal128.h | 104 | ||||
-rw-r--r-- | src/mongo/platform/decimal128_test.cpp | 137 |
3 files changed, 150 insertions, 122 deletions
diff --git a/src/mongo/platform/decimal128.cpp b/src/mongo/platform/decimal128.cpp index 0137616fd04..ebe0447b7cf 100644 --- a/src/mongo/platform/decimal128.cpp +++ b/src/mongo/platform/decimal128.cpp @@ -195,15 +195,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. @@ -300,8 +291,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 @@ -335,10 +326,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); @@ -472,12 +459,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: @@ -924,10 +911,10 @@ Decimal128 Decimal128::quantize(const Decimal128& reference, auto normalizedThis = this->normalize(); auto normalizedReferenceExponent = - static_cast<int32_t>(reference.normalize().getBiasedExponent()); + static_cast<std::int32_t>(reference.normalize().getBiasedExponent()); if (normalizedReferenceExponent != 0 && - (static_cast<int32_t>(normalizedThis.getBiasedExponent()) - normalizedReferenceExponent) > - 33) { + (static_cast<std::int32_t>(normalizedThis.getBiasedExponent()) - + normalizedReferenceExponent) > 33) { return normalizedThis; } return nonNormalizingQuantize(reference, signalingFlags, roundMode); @@ -1000,7 +987,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); @@ -1025,7 +1012,7 @@ const Decimal128 Decimal128::kSmallestNegative(1, 0, 0, 1); // Get the representation of 0 (0E0). const Decimal128 Decimal128::kNormalizedZero(Decimal128::Value( - {0, static_cast<uint64_t>(Decimal128::kExponentBias) << Decimal128::kExponentFieldPos})); + {0, static_cast<std::uint64_t>(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 052c7a020d2..e6876609336 100644 --- a/src/mongo/platform/decimal128.h +++ b/src/mongo/platform/decimal128.h @@ -33,11 +33,13 @@ #include <cstdint> #include <iostream> #include <string> +#include <type_traits> #include <utility> #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<std::uint64_t>(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 <typename T, typename = std::enable_if_t<std::is_integral_v<T>>> + 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 @@ -210,13 +215,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; @@ -229,7 +236,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; @@ -239,7 +246,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; } @@ -522,15 +529,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; @@ -545,10 +553,40 @@ 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 constructor"); + } + + return Value{coefficientLow, + (sign << kSignFieldPos) | (exponent << kExponentFieldPos) | coefficientHigh}; + } + + // The absolute value of constexpr `i` as a uint64_t, avoiding warnings. + template <typename T> + constexpr std::uint64_t _makeCoefficientLow(T i) { + IF_CONSTEXPR(std::is_signed_v<T>) { + if (i < 0) { + std::make_unsigned_t<T> ui = i; + ui = ~ui + 1; // Negation, without MSVC C4146. + return static_cast<std::uint64_t>(ui); + } else { + return static_cast<std::uint64_t>(i); + } + } + else { + return static_cast<std::uint64_t>(i); + } + } + Value _value; }; } // namespace mongo diff --git a/src/mongo/platform/decimal128_test.cpp b/src/mongo/platform/decimal128_test.cpp index 1f0362e6a3a..10029756fbb 100644 --- a/src/mongo/platform/decimal128_test.cpp +++ b/src/mongo/platform/decimal128_test.cpp @@ -46,74 +46,75 @@ TEST(Decimal128Test, TestDefaultConstructor) { 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 <typename T> +using Lim = std::numeric_limits<T>; + +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; + +#define TEST_CTOR_COMMON_(N, HIGH64, LOW64) \ + { \ + Decimal128 d{N}; \ + auto dv = d.getValue(); \ + ASSERT_EQ(dv.high64, static_cast<uint64_t>(HIGH64)); \ + ASSERT_EQ(dv.low64, static_cast<uint64_t>(LOW64)); \ + } -TEST(Decimal128Test, TestInt32ConstructorMax) { - int32_t intMax = std::numeric_limits<int32_t>::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); -} +// Veriy that constructor does the same thing whether N is constexpr or not. +// Undefined behavior in the constructor could cause divergence. +#define TEST_CTOR(N, HIGH64, LOW64) \ + { \ + constexpr auto cn = N; \ + TEST_CTOR_COMMON_(cn, HIGH64, LOW64) \ + std::add_volatile_t<decltype(cn)> vn(cn); \ + TEST_CTOR_COMMON_(vn, HIGH64, LOW64) \ + } -TEST(Decimal128Test, TestInt32ConstructorMin) { - int32_t intMin = std::numeric_limits<int32_t>::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); -} + TEST_CTOR(int8_t{0}, posHigh, 0); // +0E+0 + TEST_CTOR(Lim<int8_t>::lowest(), negHigh, uint64_t{1} << 7); + TEST_CTOR(Lim<int8_t>::max(), posHigh, (uint64_t{1} << 7) - 1); + TEST_CTOR(int8_t{5}, posHigh, 0x5); -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(uint8_t{0}, posHigh, 0); + TEST_CTOR(Lim<uint8_t>::max(), posHigh, (uint64_t{1} << 8) - 1); + TEST_CTOR(uint8_t{5}, posHigh, 0x5); -TEST(Decimal128Test, TestInt64ConstructorMax) { - int64_t longMax = std::numeric_limits<long long>::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(int16_t{0}, posHigh, 0); + TEST_CTOR(Lim<int16_t>::lowest(), negHigh, uint64_t{1} << 15); + TEST_CTOR(Lim<int16_t>::max(), posHigh, (uint64_t{1} << 15) - 1); + TEST_CTOR(int16_t{5}, posHigh, 0x5); -TEST(Decimal128Test, TestInt64ConstructorMin) { - int64_t longMin = std::numeric_limits<long long>::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(uint16_t{0}, posHigh, 0); + TEST_CTOR(Lim<uint16_t>::max(), posHigh, (uint64_t{1} << 16) - 1); + TEST_CTOR(uint16_t{5}, posHigh, 0x5); + + TEST_CTOR(int32_t{0}, posHigh, 0); + TEST_CTOR(Lim<int32_t>::lowest(), negHigh, uint64_t{1} << 31); + TEST_CTOR(Lim<int32_t>::max(), posHigh, (uint64_t{1} << 31) - 1); + TEST_CTOR(int32_t{5}, posHigh, 0x5); + + TEST_CTOR(uint32_t{0}, posHigh, 0); + TEST_CTOR(Lim<uint32_t>::max(), posHigh, (uint64_t{1} << 32) - 1); + TEST_CTOR(uint32_t{5}, posHigh, 0x5); + + TEST_CTOR(int64_t{0}, posHigh, 0); + TEST_CTOR(Lim<int64_t>::lowest(), negHigh, uint64_t{1} << 63); + TEST_CTOR(Lim<int64_t>::max(), posHigh, (uint64_t{1} << 63) - 1); + TEST_CTOR(int64_t{5}, posHigh, 0x5); + + TEST_CTOR(uint64_t{0}, posHigh, 0); + TEST_CTOR(Lim<uint64_t>::max(), posHigh, Lim<uint64_t>::max()); + TEST_CTOR(uint64_t{5}, posHigh, 0x5); + +#undef TEST_CTOR_COMMON_ +#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 +299,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 +330,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 +961,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()); } |