diff options
Diffstat (limited to 'src/mongo/util')
-rw-r--r-- | src/mongo/util/safe_num-inl.h | 4 | ||||
-rw-r--r-- | src/mongo/util/safe_num.cpp | 50 | ||||
-rw-r--r-- | src/mongo/util/safe_num.h | 25 | ||||
-rw-r--r-- | src/mongo/util/safe_num_test.cpp | 106 |
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) { |