summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2019-05-23 17:15:39 -0400
committerA. Jesse Jiryu Davis <jesse@mongodb.com>2019-06-06 12:18:31 -0400
commit0d8e5d0e79431df3f25cd49914a16e2dd61d064c (patch)
treeac8716822914c420195886f9d152b373a36b3329 /src
parentc27cafcd34190924cd62267fa8b42baf3caa69cb (diff)
downloadmongo-0d8e5d0e79431df3f25cd49914a16e2dd61d064c.tar.gz
SERVER-23077 Support Decimal128 with representAs().
Diffstat (limited to 'src')
-rw-r--r--src/mongo/platform/decimal128.cpp27
-rw-r--r--src/mongo/platform/decimal128.h4
-rw-r--r--src/mongo/util/represent_as.h134
-rw-r--r--src/mongo/util/represent_as_test.cpp173
4 files changed, 297 insertions, 41 deletions
diff --git a/src/mongo/platform/decimal128.cpp b/src/mongo/platform/decimal128.cpp
index 94581933375..18d59af7490 100644
--- a/src/mongo/platform/decimal128.cpp
+++ b/src/mongo/platform/decimal128.cpp
@@ -193,6 +193,9 @@ Decimal128::Decimal128(std::int32_t 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.
@@ -527,6 +530,30 @@ std::int64_t Decimal128::toLongExact(std::uint32_t* signalingFlags, RoundingMode
}
}
+std::uint64_t Decimal128::toULongExact(RoundingMode roundMode) const {
+ std::uint32_t throwAwayFlag = 0;
+ return toLongExact(&throwAwayFlag, roundMode);
+}
+
+std::uint64_t Decimal128::toULongExact(std::uint32_t* signalingFlags,
+ RoundingMode roundMode) const {
+ BID_UINT128 dec128 = decimal128ToLibraryType(_value);
+ switch (roundMode) {
+ case kRoundTiesToEven:
+ return bid128_to_uint64_xrnint(dec128, signalingFlags);
+ case kRoundTowardNegative:
+ return bid128_to_uint64_xfloor(dec128, signalingFlags);
+ case kRoundTowardPositive:
+ return bid128_to_uint64_xceil(dec128, signalingFlags);
+ case kRoundTowardZero:
+ return bid128_to_uint64_xint(dec128, signalingFlags);
+ case kRoundTiesToAway:
+ return bid128_to_uint64_xrninta(dec128, signalingFlags);
+ default:
+ return bid128_to_uint64_xrnint(dec128, signalingFlags);
+ }
+}
+
double Decimal128::toDouble(RoundingMode roundMode) const {
std::uint32_t throwAwayFlag = 0;
return toDouble(&throwAwayFlag, roundMode);
diff --git a/src/mongo/platform/decimal128.h b/src/mongo/platform/decimal128.h
index 90104354f37..7b96d8e2611 100644
--- a/src/mongo/platform/decimal128.h
+++ b/src/mongo/platform/decimal128.h
@@ -171,6 +171,7 @@ public:
explicit Decimal128(std::int32_t int32Value);
explicit Decimal128(std::int64_t int64Value);
+ explicit Decimal128(std::uint64_t uint64Value);
/**
* This constructor takes a double and constructs a Decimal128 object given a roundMode, either
@@ -372,6 +373,9 @@ public:
std::int64_t toLongExact(RoundingMode roundMode = kRoundTiesToEven) const;
std::int64_t toLongExact(std::uint32_t* signalingFlags,
RoundingMode roundMode = kRoundTiesToEven) const;
+ std::uint64_t toULongExact(RoundingMode roundMode = kRoundTiesToEven) const;
+ std::uint64_t toULongExact(std::uint32_t* signalingFlags,
+ RoundingMode roundMode = kRoundTiesToEven) const;
/**
* These functions convert decimals to doubles and have the ability to signal
diff --git a/src/mongo/util/represent_as.h b/src/mongo/util/represent_as.h
index e5d8f17829c..de96c5dd5ac 100644
--- a/src/mongo/util/represent_as.h
+++ b/src/mongo/util/represent_as.h
@@ -33,10 +33,12 @@
#include <limits>
#include <type_traits>
+#include <boost/numeric/conversion/cast.hpp>
#include <boost/optional.hpp>
#include "mongo/base/static_assert.h"
#include "mongo/stdx/type_traits.h"
+#include "mongo/util/if_constexpr.h"
namespace mongo {
@@ -166,6 +168,17 @@ int compare(T t, U u) {
return signedCompare(upconvert(t), upconvert(u));
}
+/**
+ * Return true if number can be converted to Output type without underflow or overflow.
+ */
+template <typename Output, typename Input>
+bool inRange(Input i) {
+ const auto floor = std::numeric_limits<Output>::lowest();
+ const auto ceiling = std::numeric_limits<Output>::max();
+
+ return detail::compare(i, floor) >= 0 && detail::compare(i, ceiling) <= 0;
+}
+
} // namespace detail
/**
@@ -180,63 +193,102 @@ int compare(T t, U u) {
* auto v3 = representAs<int>(10.3); // v3 is disengaged
*/
template <typename Output, typename Input>
-boost::optional<Output> representAs(Input number) {
- if (std::is_same<Input, Output>::value) {
- return {static_cast<Output>(number)};
+boost::optional<Output> representAs(Input number) try {
+ IF_CONSTEXPR(std::is_same_v<Input, Output>) {
+ return number;
}
-
- // If number is NaN and Output can also represent NaN, return NaN
- // Note: We need to specifically handle NaN here because of the way
- // detail::compare is implemented.
- {
- // We use ADL here to allow types, such as Decimal, to supply their
- // own definitions of isnan(). If the Input type does not define a
- // custom isnan(), then we fall back to using std::isnan().
- using std::isnan;
- if (std::is_floating_point<Input>::value && isnan(number)) {
- if (std::is_floating_point<Output>::value) {
- return {static_cast<Output>(number)};
+ else IF_CONSTEXPR(std::is_same_v<Decimal128, Output>) {
+ // Use Decimal128's ctor taking (u)int64_t or double, if it's safe to cast to one of those.
+ IF_CONSTEXPR(std::is_integral_v<Input>) {
+ IF_CONSTEXPR(std::is_signed_v<Input>) {
+ return Decimal128{boost::numeric_cast<int64_t>(number)};
+ }
+ else {
+ return Decimal128{boost::numeric_cast<uint64_t>(number)};
}
}
+ else IF_CONSTEXPR(std::is_floating_point_v<Input>) {
+ return Decimal128{boost::numeric_cast<double>(number)};
+ }
+ else {
+ return {};
+ }
}
+ else {
+ // If number is NaN and Output can also represent NaN, return NaN
+ // Note: We need to specifically handle NaN here because of the way
+ // detail::compare is implemented.
+ if (std::is_floating_point_v<Input> && std::isnan(number)) {
+ if (std::is_floating_point_v<Output>) {
+ return {static_cast<Output>(number)};
+ }
+ }
- // If Output is integral and number is a non-integral floating point value,
- // return a disengaged optional.
- if (std::is_floating_point<Input>::value && std::is_integral<Output>::value) {
- if (!(std::trunc(number) == number)) {
+ // If Output is integral and number is a non-integral floating point value,
+ // return a disengaged optional.
+ IF_CONSTEXPR(std::is_floating_point_v<Input> && std::is_integral_v<Output>) {
+ if (!(std::trunc(number) == number)) {
+ return {};
+ }
+ }
+
+ if (!detail::inRange<Output>(number)) {
return {};
}
- }
- const auto floor = std::numeric_limits<Output>::lowest();
- const auto ceiling = std::numeric_limits<Output>::max();
+ Output numberOut(number);
- // If number is out-of-bounds for Output type, fail.
- if ((detail::compare(number, floor) < 0) || (detail::compare(number, ceiling) > 0)) {
- return {};
- }
+ // Some integers cannot be exactly represented as floating point numbers.
+ // To check, we cast back to the input type if we can, and compare.
+ IF_CONSTEXPR(std::is_integral_v<Input> && std::is_floating_point_v<Output>) {
+ if (!detail::inRange<Input>(numberOut) || static_cast<Input>(numberOut) != number) {
+ return {};
+ }
+ }
- // Our number is within bounds, safe to perform a static_cast.
- auto numberOut = static_cast<Output>(number);
+ return numberOut;
+ }
+} catch (const boost::bad_numeric_cast&) {
+ return {};
+}
- // Some integers cannot be exactly represented as floating point numbers.
- // To check, we cast back to the input type if we can, and compare.
- if (std::is_integral<Input>::value && std::is_floating_point<Output>::value) {
- const auto inputFloor = std::numeric_limits<Input>::lowest();
- const auto inputCeiling = std::numeric_limits<Input>::max();
+// Overload for converting from Decimal128.
+template <typename Output>
+boost::optional<Output> representAs(const Decimal128& number) try {
+ std::uint32_t flags = 0;
+ Output numberOut;
- // If it is not safe to cast back to the Input type, fail.
- if ((detail::compare(numberOut, inputFloor) < 0) ||
- (detail::compare(numberOut, inputCeiling) > 0)) {
- return {};
+ IF_CONSTEXPR(std::is_same_v<Output, Decimal128>) {
+ return number;
+ }
+ else IF_CONSTEXPR(std::is_floating_point_v<Output>) {
+ numberOut = boost::numeric_cast<Output>(number.toDouble(&flags));
+ }
+ else IF_CONSTEXPR(std::is_integral_v<Output>) {
+ IF_CONSTEXPR(std::is_signed_v<Output>) {
+ numberOut = boost::numeric_cast<Output>(number.toLongExact(&flags));
}
-
- if (number != static_cast<Input>(numberOut)) {
- return {};
+ else {
+ numberOut = boost::numeric_cast<Output>(number.toULongExact(&flags));
}
}
+ else {
+ // Unsupported type.
+ return {};
+ }
+
+ // Decimal128::toDouble/toLongExact failed.
+ if (flags & (Decimal128::kUnderflow | Decimal128::kOverflow | Decimal128::kInvalid)) {
+ return {};
+ }
+
+ if (std::is_integral<Output>() && flags & Decimal128::kInexact) {
+ return {};
+ }
- return {static_cast<Output>(numberOut)};
+ return numberOut;
+} catch (const boost::bad_numeric_cast&) {
+ return {};
}
} // namespace mongo
diff --git a/src/mongo/util/represent_as_test.cpp b/src/mongo/util/represent_as_test.cpp
index 1c7538cb3a9..bab9ba3ca53 100644
--- a/src/mongo/util/represent_as_test.cpp
+++ b/src/mongo/util/represent_as_test.cpp
@@ -31,6 +31,7 @@
#include <cmath>
#include <limits>
+#include <type_traits>
#include <boost/optional.hpp>
@@ -246,6 +247,178 @@ TEST(RepresentAs, UnsignedIntToDouble) {
ASSERT(!(representAs<double>(kUInt64Max)));
}
+template <typename Number>
+void decimal128ToNumber() {
+ const auto floor = std::to_string(std::numeric_limits<Number>::lowest());
+ const auto ceiling = std::to_string(std::numeric_limits<Number>::max());
+
+ ASSERT_EQ(*representAs<Number>(Decimal128(floor)), std::numeric_limits<Number>::lowest());
+ ASSERT_EQ(*representAs<Number>(Decimal128(ceiling)), std::numeric_limits<Number>::max());
+
+ ASSERT_EQ(*representAs<Number>(Decimal128::kNormalizedZero), static_cast<Number>(0));
+ ASSERT_EQ(*representAs<Number>(Decimal128("5")), static_cast<Number>(5));
+
+ ASSERT(!representAs<Number>(Decimal128::kLargestPositive));
+ ASSERT(!representAs<Number>(Decimal128::kLargestNegative));
+}
+
+template <typename Float>
+void decimal128ToFloatingPoint() {
+ decimal128ToNumber<Float>();
+ ASSERT_EQ(*representAs<Float>(Decimal128("-5")), -5);
+ ASSERT_EQ(*representAs<Float>(Decimal128("5.5")), 5.5);
+ ASSERT_EQ(*representAs<Float>(Decimal128("-5.5")), -5.5);
+}
+
+TEST(RepresentAs, Decimal128ToFloat) {
+ decimal128ToFloatingPoint<float>();
+}
+
+TEST(RepresentAs, Decimal128ToDouble) {
+ decimal128ToFloatingPoint<double>();
+}
+
+template <typename Integer>
+void decimal128ToInteger() {
+ decimal128ToNumber<Integer>();
+ ASSERT(!representAs<Integer>(Decimal128("5.5")));
+ ASSERT(!representAs<Integer>(Decimal128("-5.5")));
+
+ ASSERT(!representAs<Integer>(Decimal128::kLargestPositive));
+ ASSERT(!representAs<Integer>(Decimal128::kLargestNegative));
+ ASSERT(!representAs<Integer>(Decimal128::kSmallestPositive));
+ ASSERT(!representAs<Integer>(Decimal128::kSmallestNegative));
+
+ IF_CONSTEXPR(std::is_signed<Integer>()) {
+ ASSERT_EQ(*representAs<Integer>(Decimal128("-5")), -5);
+ }
+ else {
+ ASSERT(!representAs<Integer>(Decimal128("-5")));
+ }
+}
+
+TEST(RepresentAs, Decimal128ToInt8) {
+ decimal128ToInteger<int8_t>();
+}
+
+TEST(RepresentAs, Decimal128ToUInt8) {
+ decimal128ToInteger<uint8_t>();
+}
+
+TEST(RepresentAs, Decimal128ToInt16) {
+ decimal128ToInteger<int16_t>();
+}
+
+TEST(RepresentAs, Decimal128ToUInt16) {
+ decimal128ToInteger<uint16_t>();
+}
+
+TEST(RepresentAs, Decimal128ToInt32) {
+ decimal128ToInteger<int32_t>();
+}
+
+TEST(RepresentAs, Decimal128ToUInt32) {
+ decimal128ToInteger<uint32_t>();
+}
+
+TEST(RepresentAs, Decimal128ToInt64) {
+ decimal128ToInteger<int64_t>();
+}
+
+TEST(RepresentAs, Decimal128ToUInt64) {
+ decimal128ToInteger<uint64_t>();
+}
+
+TEST(RepresentAs, Decimal128ToDecimal128) {
+ Decimal128 decimals[] = {Decimal128::kNormalizedZero,
+ Decimal128::kLargestPositive,
+ Decimal128::kSmallestPositive,
+ Decimal128::kLargestNegative,
+ Decimal128::kSmallestNegative,
+ Decimal128(5),
+ Decimal128(5.5),
+ Decimal128(-5),
+ Decimal128(-5.5)};
+
+ for (const auto& d : decimals) {
+ ASSERT(representAs<Decimal128>(d)->isEqual(d));
+ }
+}
+
+template <typename Integer>
+void integerToDecimal128() {
+ std::vector<Integer> v{
+ Integer{5}, std::numeric_limits<Integer>::lowest(), std::numeric_limits<Integer>::max()};
+
+ IF_CONSTEXPR(std::is_signed_v<Integer>) {
+ v.emplace_back(-5);
+ }
+
+ for (const auto& n : v) {
+ ASSERT(representAs<Decimal128>(n)->isEqual(Decimal128(std::to_string(n))));
+ }
+}
+
+TEST(RepresentAs, Int8ToDecimal128) {
+ integerToDecimal128<int8_t>();
+}
+
+TEST(RepresentAs, UInt8ToDecimal128) {
+ integerToDecimal128<uint8_t>();
+}
+
+TEST(RepresentAs, Int16ToDecimal128) {
+ integerToDecimal128<int16_t>();
+}
+
+TEST(RepresentAs, UInt16ToDecimal128) {
+ integerToDecimal128<uint16_t>();
+}
+
+TEST(RepresentAs, Int32ToDecimal128) {
+ integerToDecimal128<int32_t>();
+}
+
+TEST(RepresentAs, UInt32ToDecimal128) {
+ integerToDecimal128<uint32_t>();
+}
+
+TEST(RepresentAs, Int64ToDecimal128) {
+ integerToDecimal128<int64_t>();
+}
+
+TEST(RepresentAs, UInt64ToDecimal128) {
+ integerToDecimal128<uint64_t>();
+}
+
+template <typename Float>
+void floatToDecimal128() {
+ Float x = 5.5;
+ ASSERT(representAs<Decimal128>(x)->isEqual(Decimal128(std::to_string(x))));
+ ASSERT(representAs<Decimal128>(-x)->isEqual(Decimal128(std::to_string(-x))));
+
+ // Assert extreme numbers can be represented as Decimal128, and one basic property of each.
+ using limits = std::numeric_limits<Float>;
+ ASSERT(representAs<Decimal128>(limits::lowest())->isLess(Decimal128(0)));
+ ASSERT(representAs<Decimal128>(limits::min())->isGreater(Decimal128(0)));
+ ASSERT(representAs<Decimal128>(limits::denorm_min())->isGreater(Decimal128(0)));
+ ASSERT(representAs<Decimal128>(limits::max())->isGreater(Decimal128(0)));
+ ASSERT(representAs<Decimal128>(limits::quiet_NaN())->isNaN());
+ ASSERT(representAs<Decimal128>(-limits::quiet_NaN())->isNaN());
+ ASSERT(representAs<Decimal128>(limits::infinity())->isInfinite());
+ ASSERT(representAs<Decimal128>(-limits::infinity())->isInfinite());
+ ASSERT(representAs<Decimal128>(Float{0})->isEqual(Decimal128(0)));
+ ASSERT(representAs<Decimal128>(Float{-0})->isEqual(Decimal128(0)));
+}
+
+TEST(RepresentAs, FloatToDecimal128) {
+ floatToDecimal128<float>();
+}
+
+TEST(RepresentAs, DoubleToDecimal128) {
+ floatToDecimal128<double>();
+}
+
TEST(RepresentAs, PlatformDependent) {
// signed char
ASSERT(*(representAs<int>(kCharMax)) == kCharMaxAsInt);