summaryrefslogtreecommitdiff
path: root/src/mongo/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/util')
-rw-r--r--src/mongo/util/safe_num-inl.h4
-rw-r--r--src/mongo/util/safe_num.cpp50
-rw-r--r--src/mongo/util/safe_num.h25
-rw-r--r--src/mongo/util/safe_num_test.cpp106
4 files changed, 175 insertions, 10 deletions
diff --git a/src/mongo/util/safe_num-inl.h b/src/mongo/util/safe_num-inl.h
index 0327fa54b00..8ac16756f17 100644
--- a/src/mongo/util/safe_num-inl.h
+++ b/src/mongo/util/safe_num-inl.h
@@ -53,6 +53,10 @@ inline SafeNum::SafeNum(double num) : _type(NumberDouble) {
_value.doubleVal = num;
}
+inline SafeNum::SafeNum(Decimal128 num) : _type(NumberDecimal) {
+ _value.decimalVal = num.getValue();
+}
+
inline bool SafeNum::operator==(const SafeNum& rhs) const {
return isEquivalent(rhs);
}
diff --git a/src/mongo/util/safe_num.cpp b/src/mongo/util/safe_num.cpp
index 8976f27a251..f49fcd7b53a 100644
--- a/src/mongo/util/safe_num.cpp
+++ b/src/mongo/util/safe_num.cpp
@@ -51,6 +51,10 @@ SafeNum::SafeNum(const BSONElement& element) {
_type = NumberDouble;
_value.doubleVal = element.Double();
break;
+ case NumberDecimal:
+ _type = NumberDecimal;
+ _value.decimalVal = element.Decimal().getValue();
+ break;
default:
_type = EOO;
}
@@ -68,6 +72,9 @@ std::string SafeNum::debugString() const {
case NumberDouble:
os << "(NumberDouble)" << _value.doubleVal;
break;
+ case NumberDecimal:
+ os << "(NumberDecimal)" << getDecimal(*this).toString();
+ break;
case EOO:
os << "(EOO)";
break;
@@ -99,6 +106,14 @@ bool SafeNum::isEquivalent(const SafeNum& rhs) const {
// If the types of either side are mixed, we'll try to find the shortest type we
// can upconvert to that would not sacrifice the accuracy in the process.
+ // If one side is a decimal, compare both sides as decimals.
+ if (_type == NumberDecimal || rhs._type == NumberDecimal) {
+ // Note: isEqual is faster than using compareDecimals, however it does not handle
+ // comparing NaN as equal (differing from BSONElement::woCompare). This case
+ // is not handled for double comparison above eihter.
+ return getDecimal(*this).isEqual(getDecimal(rhs));
+ }
+
// If none of the sides is a double, compare them as long's.
if (_type != NumberDouble && rhs._type != NumberDouble) {
return getLongLong(*this) == getLongLong(rhs);
@@ -134,6 +149,8 @@ bool SafeNum::isIdentical(const SafeNum& rhs) const {
return _value.int64Val == rhs._value.int64Val;
case NumberDouble:
return _value.doubleVal == rhs._value.doubleVal;
+ case NumberDecimal:
+ return Decimal128(_value.decimalVal).isEqual(rhs._value.decimalVal);
case EOO:
// EOO doesn't match anything, including itself.
default:
@@ -160,6 +177,23 @@ double SafeNum::getDouble(const SafeNum& snum) {
return snum._value.int64Val;
case NumberDouble:
return snum._value.doubleVal;
+ case NumberDecimal:
+ return Decimal128(snum._value.decimalVal).toDouble();
+ default:
+ return 0.0;
+ }
+}
+
+Decimal128 SafeNum::getDecimal(const SafeNum& snum) {
+ switch (snum._type) {
+ case NumberInt:
+ return snum._value.int32Val;
+ case NumberLong:
+ return snum._value.int64Val;
+ case NumberDouble:
+ return snum._value.doubleVal;
+ case NumberDecimal:
+ return snum._value.decimalVal;
default:
return 0.0;
}
@@ -211,6 +245,10 @@ SafeNum addFloats(double lDouble, double rDouble) {
return SafeNum(sum);
}
+SafeNum addDecimals(Decimal128 lDecimal, Decimal128 rDecimal) {
+ return SafeNum(lDecimal.add(rDecimal));
+}
+
SafeNum mulInt32Int32(int lInt32, int rInt32) {
// NOTE: Please see "Secure Coding in C and C++", Second Edition, page 264-265 for
// details on this algorithm (for an alternative resources, see
@@ -274,6 +312,10 @@ SafeNum mulFloats(double lDouble, double rDouble) {
return SafeNum(product);
}
+SafeNum mulDecimals(Decimal128 lDecimal, Decimal128 rDecimal) {
+ return SafeNum(lDecimal.multiply(rDecimal));
+}
+
} // namespace
SafeNum SafeNum::addInternal(const SafeNum& lhs, const SafeNum& rhs) {
@@ -296,6 +338,10 @@ SafeNum SafeNum::addInternal(const SafeNum& lhs, const SafeNum& rhs) {
return addInt64Int64(lhs._value.int64Val, rhs._value.int64Val);
}
+ if (lType == NumberDecimal || rType == NumberDecimal) {
+ return addDecimals(getDecimal(lhs), getDecimal(rhs));
+ }
+
if ((lType == NumberInt || lType == NumberLong || lType == NumberDouble) &&
(rType == NumberInt || rType == NumberLong || rType == NumberDouble)) {
return addFloats(getDouble(lhs), getDouble(rhs));
@@ -324,6 +370,10 @@ SafeNum SafeNum::mulInternal(const SafeNum& lhs, const SafeNum& rhs) {
return mulInt64Int64(lhs._value.int64Val, rhs._value.int64Val);
}
+ if (lType == NumberDecimal || rType == NumberDecimal) {
+ return mulDecimals(getDecimal(lhs), getDecimal(rhs));
+ }
+
if ((lType == NumberInt || lType == NumberLong || lType == NumberDouble) &&
(rType == NumberInt || rType == NumberLong || rType == NumberDouble)) {
return mulFloats(getDouble(lhs), getDouble(rhs));
diff --git a/src/mongo/util/safe_num.h b/src/mongo/util/safe_num.h
index 839996bd370..8151d5aa4d4 100644
--- a/src/mongo/util/safe_num.h
+++ b/src/mongo/util/safe_num.h
@@ -43,7 +43,8 @@ class Document;
* SafeNum holds and does arithmetic on a number in a safe way, handling overflow
* and casting for the user. 32-bit integers will overflow into 64-bit integers. But
* 64-bit integers will NOT overflow to doubles. Also, this class does NOT
- * downcast. This class should be as conservative as possible about upcasting, but
+ * downcast. Doubles will NOT overflow to decimal, but mixed type arithmetic with a decimal
+ * will. This class should be as conservative as possible about upcasting, but
* should never lose precision.
*
* This class does not throw any exceptions, so the user should call type() before
@@ -82,6 +83,7 @@ public:
SafeNum(int num);
SafeNum(long long int num);
SafeNum(double num);
+ SafeNum(Decimal128 num);
// TODO: add Paul's mutablebson::Element ctor
//
@@ -90,8 +92,9 @@ public:
/**
* Returns true if the numeric quantity of 'rhs' and 'this' are the same. That is,
- * an int32(10), an int64(10), and a double(10) are equivalent. An EOO-typed safe
- * num is equivalent only to another EOO-typed instance. Otherwise, returns false.
+ * an int32(10), an int64(10), a double(10), and a decimal(10) are equivalent. An
+ * EOO-typed safe num is equivalent only to another EOO-typed instance. Otherwise,
+ * returns false.
*/
bool isEquivalent(const SafeNum& rhs) const;
bool operator==(const SafeNum& rhs) const;
@@ -124,8 +127,8 @@ public:
//
// logical operation support. Note that these operations are only supported for
- // integral types. Attempts to apply with either side holding a double value
- // will result in an EOO typed safenum.
+ // integral types. Attempts to apply with either side holding a double or decimal
+ // value will result in an EOO typed safenum.
//
// Bitwise 'and' support
@@ -170,7 +173,7 @@ public:
static const long long maxIntInDouble = 9007199254740992LL; // 2^53
private:
- // One of the following: NumberInt, NumberLong, NumberDouble, or EOO.
+ // One of the following: NumberInt, NumberLong, NumberDouble, NumberDecimal, or EOO.
BSONType _type;
// Value of the safe num. Indeterminate if _type is EOO.
@@ -178,6 +181,7 @@ private:
int int32Val;
long long int int64Val;
double doubleVal;
+ Decimal128::Value decimalVal;
} _value;
/**
@@ -211,7 +215,7 @@ private:
/**
* Extracts the value of 'snum' in a long format. It assumes 'snum' is an NumberInt
- * or a NumberDouble.
+ * or a NumberLong.
*/
static long long getLongLong(const SafeNum& snum);
@@ -220,6 +224,13 @@ private:
* SafeNum, i.e., that _type is not EOO.
*/
static double getDouble(const SafeNum& snum);
+
+ /**
+ * Extracts the value of 'snum' in a Decimal128 format. It assumes 'snum' is an
+ * NumberInt, NumberDouble, or NumberLong. Integral values are converted exactly.
+ * NumberDouble is converted to 15 digits of precision, as defined in Decimal128.
+ */
+ static Decimal128 getDecimal(const SafeNum& snum);
};
// Convenience method for unittest code. Please use accessors otherwise.
diff --git a/src/mongo/util/safe_num_test.cpp b/src/mongo/util/safe_num_test.cpp
index ff910f7b3fc..7d679bdb3d1 100644
--- a/src/mongo/util/safe_num_test.cpp
+++ b/src/mongo/util/safe_num_test.cpp
@@ -31,12 +31,15 @@
#undef MONGO_PCH_WHITELISTED // for malloc/realloc pulled from bson
#include "mongo/bson/bsontypes.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/platform/decimal128.h"
#include "mongo/util/safe_num.h"
#include "mongo/unittest/unittest.h"
namespace {
using mongo::SafeNum;
+using mongo::Decimal128;
TEST(Basics, Initialization) {
const SafeNum numInt(0);
@@ -47,6 +50,35 @@ TEST(Basics, Initialization) {
const SafeNum numDouble(0.0);
ASSERT_EQUALS(numDouble.type(), mongo::NumberDouble);
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum numDecimal(Decimal128("1.0"));
+ ASSERT_EQUALS(numDecimal.type(), mongo::NumberDecimal);
+ }
+}
+
+TEST(Basics, BSONElementInitialization) {
+ mongo::BSONObj o;
+ if (mongo::Decimal128::enabled) {
+ o = BSON("numberInt" << 1 << "numberLong" << 1LL << "numberDouble" << 0.1 << "NumberDecimal"
+ << Decimal128("1"));
+ } else {
+ o = BSON("numberInt" << 1 << "numberLong" << 1LL << "numberDouble" << 0.1);
+ }
+
+ const SafeNum numInt(o.getField("numberInt"));
+ ASSERT_EQUALS(numInt.type(), mongo::NumberInt);
+
+ const SafeNum numLong(o.getField("numberLong"));
+ ASSERT_EQUALS(numLong.type(), mongo::NumberLong);
+
+ const SafeNum numDouble(o.getField("numberDouble"));
+ ASSERT_EQUALS(numDouble.type(), mongo::NumberDouble);
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum numDecimal(o.getField("NumberDecimal"));
+ ASSERT_EQUALS(numDecimal.type(), mongo::NumberDecimal);
+ }
}
TEST(Comparison, EOO) {
@@ -70,6 +102,13 @@ TEST(Comparison, StrictTypeComparison) {
ASSERT_FALSE(one.isIdentical(oneLong));
ASSERT_FALSE(oneLong.isIdentical(oneDouble));
ASSERT_FALSE(oneDouble.isIdentical(one));
+ ASSERT_TRUE(oneDouble.isIdentical(oneDouble));
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum oneDecimal(Decimal128(1));
+ ASSERT_FALSE(oneDecimal.isIdentical(one));
+ ASSERT_TRUE(oneDecimal.isIdentical(oneDecimal));
+ }
}
TEST(Comparison, EquivalenceComparisonNormal) {
@@ -79,6 +118,11 @@ TEST(Comparison, EquivalenceComparisonNormal) {
ASSERT_EQUALS(one, oneLong);
ASSERT_EQUALS(oneLong, oneDouble);
ASSERT_EQUALS(oneDouble, one);
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum oneDecimal(Decimal128(1));
+ ASSERT_EQUALS(oneDecimal, one);
+ }
}
TEST(Comparison, MaxIntInDouble) {
@@ -111,12 +155,26 @@ TEST(Addition, UpConvertion) {
ASSERT_EQUALS((zeroInt32 + zeroDouble).type(), mongo::NumberDouble);
ASSERT_EQUALS((zeroInt64 + zeroDouble).type(), mongo::NumberDouble);
+
const SafeNum stillInt32(zeroInt32 + zeroInt32);
const SafeNum stillInt64(zeroInt64 + zeroInt64);
const SafeNum stillDouble(zeroDouble + zeroDouble);
ASSERT_EQUALS(stillInt32.type(), mongo::NumberInt);
ASSERT_EQUALS(stillInt64.type(), mongo::NumberLong);
ASSERT_EQUALS(stillDouble.type(), mongo::NumberDouble);
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum zeroDecimal(Decimal128(0));
+ ASSERT_EQUALS((zeroInt64 + zeroDecimal).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroInt32 + zeroDecimal).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDouble + zeroDecimal).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDecimal + zeroInt32).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDecimal + zeroInt64).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDecimal + zeroDouble).type(), mongo::NumberDecimal);
+
+ const SafeNum stillDecimal(zeroDecimal + zeroDecimal);
+ ASSERT_EQUALS(stillDecimal.type(), mongo::NumberDecimal);
+ }
}
TEST(Addition, Overflow32to64) {
@@ -191,7 +249,7 @@ TEST(Addition, Negative64toDouble) {
ASSERT_NOT_EQUALS(int64MinusOne, doubleResult);
}
-TEST(BitAnd, DoubleIsIgnored) {
+TEST(BitAnd, FloatingPointIsIgnored) {
const SafeNum val_int(static_cast<int>(1));
const SafeNum val_ll(static_cast<long long>(1));
const SafeNum val_double(1.0);
@@ -200,6 +258,16 @@ TEST(BitAnd, DoubleIsIgnored) {
ASSERT_FALSE((val_ll & val_double).isValid());
ASSERT_FALSE((val_double & val_ll).isValid());
ASSERT_FALSE((val_double & val_double).isValid());
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum val_decimal(Decimal128(1));
+ ASSERT_FALSE((val_int & val_decimal).isValid());
+ ASSERT_FALSE((val_double & val_decimal).isValid());
+ ASSERT_FALSE((val_ll & val_decimal).isValid());
+ ASSERT_FALSE((val_decimal & val_int).isValid());
+ ASSERT_FALSE((val_decimal & val_ll).isValid());
+ ASSERT_FALSE((val_decimal & val_double).isValid());
+ }
}
TEST(BitAnd, 32and32) {
@@ -235,7 +303,7 @@ TEST(BitAnd, MixedSize) {
ASSERT_TRUE(expected.isIdentical(result_b_s));
}
-TEST(BitOr, DoubleIsIgnored) {
+TEST(BitOr, FloatingPointIsIgnored) {
const SafeNum val_int(static_cast<int>(1));
const SafeNum val_ll(static_cast<long long>(1));
const SafeNum val_double(1.0);
@@ -244,6 +312,16 @@ TEST(BitOr, DoubleIsIgnored) {
ASSERT_FALSE((val_ll | val_double).isValid());
ASSERT_FALSE((val_double | val_ll).isValid());
ASSERT_FALSE((val_double | val_double).isValid());
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum val_decimal(Decimal128(1));
+ ASSERT_FALSE((val_decimal | val_int).isValid());
+ ASSERT_FALSE((val_decimal | val_double).isValid());
+ ASSERT_FALSE((val_decimal | val_ll).isValid());
+ ASSERT_FALSE((val_int | val_decimal).isValid());
+ ASSERT_FALSE((val_ll | val_decimal).isValid());
+ ASSERT_FALSE((val_double | val_decimal).isValid());
+ }
}
TEST(BitOr, 32and32) {
@@ -278,7 +356,7 @@ TEST(BitOr, MixedSize) {
ASSERT_TRUE(expected.isIdentical(result_b_s));
}
-TEST(BitXor, DoubleIsIgnored) {
+TEST(BitXor, FloatingPointIsIgnored) {
const SafeNum val_int(static_cast<int>(1));
const SafeNum val_ll(static_cast<long long>(1));
const SafeNum val_double(1.0);
@@ -287,6 +365,16 @@ TEST(BitXor, DoubleIsIgnored) {
ASSERT_FALSE((val_ll ^ val_double).isValid());
ASSERT_FALSE((val_double ^ val_ll).isValid());
ASSERT_FALSE((val_double ^ val_double).isValid());
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum val_decimal(Decimal128(1));
+ ASSERT_FALSE((val_decimal ^ val_int).isValid());
+ ASSERT_FALSE((val_decimal ^ val_ll).isValid());
+ ASSERT_FALSE((val_decimal ^ val_double).isValid());
+ ASSERT_FALSE((val_int ^ val_decimal).isValid());
+ ASSERT_FALSE((val_ll ^ val_decimal).isValid());
+ ASSERT_FALSE((val_double ^ val_decimal).isValid());
+ }
}
TEST(BitXor, 32and32) {
@@ -374,6 +462,18 @@ TEST(Multiplication, UpConvertion) {
ASSERT_EQUALS(stillInt32.type(), mongo::NumberInt);
ASSERT_EQUALS(stillInt64.type(), mongo::NumberLong);
ASSERT_EQUALS(stillDouble.type(), mongo::NumberDouble);
+
+ if (mongo::Decimal128::enabled) {
+ const SafeNum zeroDecimal(Decimal128(0));
+ ASSERT_EQUALS((zeroDecimal * zeroInt32).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroInt32 * zeroDecimal).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDecimal * zeroInt64).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroInt64 * zeroDecimal).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDecimal * zeroDouble).type(), mongo::NumberDecimal);
+ ASSERT_EQUALS((zeroDouble * zeroDecimal).type(), mongo::NumberDecimal);
+ const SafeNum stillDecimal(zeroDecimal * zeroDecimal);
+ ASSERT_EQUALS(stillDecimal.type(), mongo::NumberDecimal);
+ }
}
TEST(Multiplication, Overflow32to64) {