summaryrefslogtreecommitdiff
path: root/src/mongo/platform
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2019-06-17 15:43:05 -0400
committerBilly Donahue <billy.donahue@mongodb.com>2019-06-27 17:03:26 -0400
commit644fc54e18049e0737ae1621aec5eb7afbe1934f (patch)
tree4ea4c21b4ee88ff9b46e891a122e8e249691fae4 /src/mongo/platform
parentfd8538074a35666a8f18f54ffae798095cbc43f9 (diff)
downloadmongo-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.cpp31
-rw-r--r--src/mongo/platform/decimal128.h104
-rw-r--r--src/mongo/platform/decimal128_test.cpp137
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());
}