diff options
-rw-r--r-- | src/mongo/bson/mutable/const_element-inl.h | 4 | ||||
-rw-r--r-- | src/mongo/bson/mutable/const_element.h | 1 | ||||
-rw-r--r-- | src/mongo/bson/mutable/document.cpp | 27 | ||||
-rw-r--r-- | src/mongo/bson/mutable/document.h | 3 | ||||
-rw-r--r-- | src/mongo/bson/mutable/element-inl.h | 5 | ||||
-rw-r--r-- | src/mongo/bson/mutable/element.cpp | 4 | ||||
-rw-r--r-- | src/mongo/bson/mutable/element.h | 11 | ||||
-rw-r--r-- | src/mongo/bson/mutable/mutable_bson_test.cpp | 157 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_algo_test.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser_leaf_test.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_bit_test.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_inc_test.cpp | 123 | ||||
-rw-r--r-- | src/mongo/platform/decimal128.h | 2 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8.cpp | 2 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 2 | ||||
-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 |
20 files changed, 559 insertions, 18 deletions
diff --git a/src/mongo/bson/mutable/const_element-inl.h b/src/mongo/bson/mutable/const_element-inl.h index 562a47c16a0..325df232b09 100644 --- a/src/mongo/bson/mutable/const_element-inl.h +++ b/src/mongo/bson/mutable/const_element-inl.h @@ -148,6 +148,10 @@ inline int64_t ConstElement::getValueLong() const { return _basis.getValueLong(); } +inline Decimal128 ConstElement::getValueDecimal() const { + return _basis.getValueDecimal(); +} + inline bool ConstElement::isValueMinKey() const { return _basis.isValueMinKey(); } diff --git a/src/mongo/bson/mutable/const_element.h b/src/mongo/bson/mutable/const_element.h index 3c6b71d593a..80ad1aac724 100644 --- a/src/mongo/bson/mutable/const_element.h +++ b/src/mongo/bson/mutable/const_element.h @@ -83,6 +83,7 @@ public: inline int32_t getValueInt() const; inline Timestamp getValueTimestamp() const; inline int64_t getValueLong() const; + inline Decimal128 getValueDecimal() const; inline bool isValueMinKey() const; inline bool isValueMaxKey() const; inline SafeNum getValueSafeNum() const; diff --git a/src/mongo/bson/mutable/document.cpp b/src/mongo/bson/mutable/document.cpp index f07073ad5e0..307177db043 100644 --- a/src/mongo/bson/mutable/document.cpp +++ b/src/mongo/bson/mutable/document.cpp @@ -1450,7 +1450,7 @@ bool Element::isNumeric() const { const ElementRep& thisRep = impl.getElementRep(_repIdx); const BSONType type = impl.getType(thisRep); return ((type == mongo::NumberLong) || (type == mongo::NumberInt) || - (type == mongo::NumberDouble)); + (type == mongo::NumberDouble) || (type == mongo::NumberDecimal)); } bool Element::isIntegral() const { @@ -1478,6 +1478,8 @@ SafeNum Element::getValueSafeNum() const { return static_cast<long long int>(getValueLong()); case mongo::NumberDouble: return getValueDouble(); + case mongo::NumberDecimal: + return getValueDecimal(); default: return SafeNum(); } @@ -1845,6 +1847,15 @@ Status Element::setValueLong(const int64_t value) { return setValue(newValue._repIdx); } +Status Element::setValueDecimal(const Decimal128 value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementDecimal(fieldName, value); + return setValue(newValue._repIdx); +} + Status Element::setValueMinKey() { verify(ok()); Document::Impl& impl = getDocument().getImpl(); @@ -1888,6 +1899,8 @@ Status Element::setValueSafeNum(const SafeNum value) { return setValueLong(value._value.int64Val); case mongo::NumberDouble: return setValueDouble(value._value.doubleVal); + case mongo::NumberDecimal: + return setValueDecimal(value._value.decimalVal); default: return Status(ErrorCodes::UnsupportedFormat, "Don't know how to handle unexpected SafeNum type"); @@ -2444,6 +2457,16 @@ Element Document::makeElementLong(StringData fieldName, const int64_t value) { return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); } +Element Document::makeElementDecimal(StringData fieldName, const Decimal128 value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + Element Document::makeElementMinKey(StringData fieldName) { Impl& impl = getImpl(); dassert(impl.doesNotAlias(fieldName)); @@ -2516,6 +2539,8 @@ Element Document::makeElementSafeNum(StringData fieldName, SafeNum value) { return makeElementLong(fieldName, value._value.int64Val); case mongo::NumberDouble: return makeElementDouble(fieldName, value._value.doubleVal); + case mongo::NumberDecimal: + return makeElementDecimal(fieldName, value._value.decimalVal); default: // Return an invalid element to indicate that we failed. return end(); diff --git a/src/mongo/bson/mutable/document.h b/src/mongo/bson/mutable/document.h index 3a24eac74cc..1efd9e95729 100644 --- a/src/mongo/bson/mutable/document.h +++ b/src/mongo/bson/mutable/document.h @@ -377,6 +377,9 @@ public: /** Create a new long integer Element with the given value and field name. */ Element makeElementLong(StringData fieldName, int64_t value); + /** Create a new dec128 Element with the given value and field name. */ + Element makeElementDecimal(StringData fieldName, Decimal128 value); + /** Create a new min key Element with the given field name. */ Element makeElementMinKey(StringData fieldName); diff --git a/src/mongo/bson/mutable/element-inl.h b/src/mongo/bson/mutable/element-inl.h index 6dd2810e43c..c88ad2503c5 100644 --- a/src/mongo/bson/mutable/element-inl.h +++ b/src/mongo/bson/mutable/element-inl.h @@ -101,6 +101,11 @@ inline int64_t Element::getValueLong() const { return getValue()._numberLong(); } +inline Decimal128 Element::getValueDecimal() const { + dassert(hasValue() && isType(mongo::NumberDecimal)); + return getValue()._numberDecimal(); +} + inline bool Element::isValueMinKey() const { return isType(mongo::MinKey); } diff --git a/src/mongo/bson/mutable/element.cpp b/src/mongo/bson/mutable/element.cpp index 4ff1959559f..05cac77de80 100644 --- a/src/mongo/bson/mutable/element.cpp +++ b/src/mongo/bson/mutable/element.cpp @@ -131,6 +131,10 @@ Status Element::appendTimestamp(StringData fieldName, Timestamp value) { return pushBack(getDocument().makeElementTimestamp(fieldName, value)); } +Status Element::appendDecimal(StringData fieldName, Decimal128 value) { + return pushBack(getDocument().makeElementDecimal(fieldName, value)); +} + Status Element::appendLong(StringData fieldName, int64_t value) { return pushBack(getDocument().makeElementLong(fieldName, value)); } diff --git a/src/mongo/bson/mutable/element.h b/src/mongo/bson/mutable/element.h index 673180c0c64..68bf99dfc08 100644 --- a/src/mongo/bson/mutable/element.h +++ b/src/mongo/bson/mutable/element.h @@ -258,7 +258,7 @@ public: bool hasValue() const; /** Returns true if this element is a numeric type (e.g. NumberLong). Currently, the - * only numeric BSON types are NumberLong, NumberInt, and NumberDouble. + * only numeric BSON types are NumberLong, NumberInt, NumberDouble, and NumberDecimal. */ bool isNumeric() const; @@ -324,6 +324,9 @@ public: /** Get the value from a long valued Element. */ inline int64_t getValueLong() const; + /** Get the value from a decimal valued Element. */ + inline Decimal128 getValueDecimal() const; + /** Returns true if this Element is the min key type. */ inline bool isValueMinKey() const; @@ -444,6 +447,9 @@ public: /** Set the value of this Element to the given long integer */ Status setValueLong(int64_t value); + /** Set the value of this Element to the given decimal. */ + Status setValueDecimal(Decimal128 value); + /** Set the value of this Element to MinKey. */ Status setValueMinKey(); @@ -567,6 +573,9 @@ public: /** Append the provided long integer as a new field with the provided name. */ Status appendLong(StringData fieldName, int64_t value); + /** Append the provided decimal as a new field with the provided name. */ + Status appendDecimal(StringData fieldName, Decimal128 value); + /** Append a max key as a new field with the provided name. */ Status appendMinKey(StringData fieldName); diff --git a/src/mongo/bson/mutable/mutable_bson_test.cpp b/src/mongo/bson/mutable/mutable_bson_test.cpp index f8086da510f..f44afe2d9df 100644 --- a/src/mongo/bson/mutable/mutable_bson_test.cpp +++ b/src/mongo/bson/mutable/mutable_bson_test.cpp @@ -35,6 +35,7 @@ #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/bson/mutable/damage_vector.h" #include "mongo/db/json.h" +#include "mongo/platform/decimal128.h" #include "mongo/unittest/unittest.h" namespace { @@ -562,6 +563,11 @@ TEST(Element, setters) { t0.setValueDouble(123.45); ASSERT_EQUALS(mongo::NumberDouble, t0.getType()); + if (mongo::Decimal128::enabled) { + t0.setValueDecimal(mongo::Decimal128("123.45E1234")); + ASSERT_EQUALS(mongo::NumberDecimal, t0.getType()); + } + t0.setValueOID(mongo::OID("47cc67093475061e3d95369d")); ASSERT_EQUALS(mongo::jstOID, t0.getType()); @@ -606,6 +612,43 @@ TEST(Element, toString) { ASSERT_FALSE(docChild.ok()); } +TEST(DecimalType, createElement) { + if (mongo::Decimal128::enabled) { + mmb::Document doc; + + mmb::Element d0 = doc.makeElementDecimal("d0", mongo::Decimal128("12345")); + ASSERT_TRUE(mongo::Decimal128("12345").isEqual(d0.getValueDecimal())); + } +} + +TEST(DecimalType, setElement) { + if (mongo::Decimal128::enabled) { + mmb::Document doc; + + mmb::Element d0 = doc.makeElementDecimal("d0", mongo::Decimal128("128")); + d0.setValueDecimal(mongo::Decimal128("123456")); + ASSERT_TRUE(mongo::Decimal128("123456").isEqual(d0.getValueDecimal())); + + d0.setValueDouble(0.1); + ASSERT_EQUALS(0.1, d0.getValueDouble()); + d0.setValueDecimal(mongo::Decimal128("23")); + ASSERT_TRUE(mongo::Decimal128("23").isEqual(d0.getValueDecimal())); + } +} + +TEST(DecimalType, appendElement) { + if (mongo::Decimal128::enabled) { + mmb::Document doc; + + mmb::Element d0 = doc.makeElementObject("e0"); + d0.appendDecimal("precision", mongo::Decimal128(34)); + + mmb::Element it = mmb::findFirstChildNamed(d0, "precision"); + ASSERT_TRUE(it.ok()); + ASSERT_TRUE(mongo::Decimal128(34).isEqual(it.getValueDecimal())); + } +} + TEST(TimestampType, createElement) { mmb::Document doc; @@ -671,6 +714,13 @@ TEST(SafeNumType, getSafeNum) { ASSERT_EQUALS(123.456789, t0.getValueDouble()); num = t0.getValueSafeNum(); ASSERT_EQUALS(num, 123.456789); + + if (mongo::Decimal128::enabled) { + t0.setValueDecimal(mongo::Decimal128("12345678.1234")); + ASSERT_TRUE(mongo::Decimal128("12345678.1234").isEqual(t0.getValueDecimal())); + num = t0.getValueSafeNum(); + ASSERT_EQUALS(num, mongo::Decimal128("12345678.1234")); + } } TEST(SafeNumType, setSafeNum) { @@ -744,8 +794,30 @@ static const char jsonSample[] = "pattern:/match.*this/," "lastfield:\"last\"}"; +static const char jsonSampleWithDecimal[] = + "{_id:ObjectId(\"47cc67093475061e3d95369d\")," + "query:\"kate hudson\"," + "owner:1234567887654321," + "date:\"2011-05-13T14:22:46.777Z\"," + "score:123.456," + "decimal:NumberDecimal(\"2\")," + "field1:Infinity," + "\"field2\":-Infinity," + "\"field3\":NaN," + "users:[" + "{uname:\"@aaaa\",editid:\"123\",date:1303959350,yes_votes:0,no_votes:0}," + "{uname:\"@bbbb\",editid:\"456\",date:1303959350,yes_votes:0,no_votes:0}," + "{uname:\"@cccc\",editid:\"789\",date:1303959350,yes_votes:0,no_votes:0}]," + "pattern:/match.*this/," + "lastfield:\"last\"}"; + TEST(Serialization, RoundTrip) { - mongo::BSONObj obj = mongo::fromjson(jsonSample); + mongo::BSONObj obj; + if (mongo::Decimal128::enabled) { + obj = mongo::fromjson(jsonSampleWithDecimal); + } else { + obj = mongo::fromjson(jsonSample); + } mmb::Document doc(obj.copy()); mongo::BSONObj built = doc.getObject(); ASSERT_EQUALS(obj, built); @@ -1313,6 +1385,11 @@ TEST(Element, IsNumeric) { elt = doc.makeElementDouble("dummy", 42.0); ASSERT_TRUE(elt.isNumeric()); + + if (mongo::Decimal128::enabled) { + elt = doc.makeElementDecimal("dummy", mongo::Decimal128(20)); + ASSERT_TRUE(elt.isNumeric()); + } } TEST(Element, IsIntegral) { @@ -1332,6 +1409,11 @@ TEST(Element, IsIntegral) { elt = doc.makeElementDouble("dummy", 42.0); ASSERT_FALSE(elt.isIntegral()); + + if (mongo::Decimal128::enabled) { + elt = doc.makeElementDecimal("dummy", mongo::Decimal128(20)); + ASSERT_FALSE(elt.isIntegral()); + } } TEST(Document, ArraySerialization) { @@ -2330,6 +2412,48 @@ TEST(TypeSupport, EncodingEquivalenceLong) { ASSERT_TRUE(identical(b.getValue(), c.getValue())); } +TEST(TypeSupport, EncodingEquivalenceDecimal) { + if (mongo::Decimal128::enabled) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::Decimal128 value1 = mongo::Decimal128(2); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::NumberDecimal); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendDecimal(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::NumberDecimal); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(value1.isEqual(mmb::ConstElement(a).getValueDecimal())); + + // Construct via call passong BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::NumberDecimal); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueDecimal(value1); + ASSERT_EQUALS(c.getType(), mongo::NumberDecimal); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); + } +} + TEST(TypeSupport, EncodingEquivalenceMinKey) { mongo::BSONObjBuilder builder; const char name[] = "thing"; @@ -2853,6 +2977,37 @@ TEST(DocumentInPlace, NumberDoubleLifecycle) { // ASSERT_EQUALS(value1, x.getValueDouble()); } +TEST(DocumentInPlace, NumberDecimalLifecycle) { + if (mongo::Decimal128::enabled) { + const mongo::Decimal128 value1 = mongo::Decimal128(32); + const mongo::Decimal128 value2 = mongo::Decimal128(2); + + mongo::BSONObj obj(BSON("x" << value1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueDecimal(value2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::NumberDecimal)); + ASSERT_TRUE(value2.isEqual(x.getValueDecimal())); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueDecimal(value1); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::NumberDecimal)); + // ASSERT_TRUE(value1.isEqual(x.getValueDecimal())); + } +} + // Doubles and longs are the same size, 8 bytes, so we should be able to do in-place // updates between them. TEST(DocumentInPlace, DoubleToLongAndBack) { diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp index 9effbf6b0b9..e3fb3e504ea 100644 --- a/src/mongo/db/matcher/expression_algo_test.cpp +++ b/src/mongo/db/matcher/expression_algo_test.cpp @@ -37,6 +37,7 @@ #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_algo.h" #include "mongo/db/matcher/expression_parser.h" +#include "mongo/platform/decimal128.h" namespace mongo { @@ -121,6 +122,21 @@ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { ASSERT_FALSE(expression::isSubsetOf(gt.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); + + if (Decimal128::enabled) { + ParsedMatchExpression decNan("{x : NumberDecimal(\"NaN\") }"); + ASSERT_TRUE(expression::isSubsetOf(decNan.get(), decNan.get())); + ASSERT_TRUE(expression::isSubsetOf(nan.get(), decNan.get())); + ASSERT_TRUE(expression::isSubsetOf(decNan.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(decNan.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(lt.get(), decNan.get())); + ASSERT_FALSE(expression::isSubsetOf(decNan.get(), lte.get())); + ASSERT_FALSE(expression::isSubsetOf(lte.get(), decNan.get())); + ASSERT_FALSE(expression::isSubsetOf(decNan.get(), gte.get())); + ASSERT_FALSE(expression::isSubsetOf(gte.get(), decNan.get())); + ASSERT_FALSE(expression::isSubsetOf(decNan.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(gt.get(), decNan.get())); + } } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp index 79836a43f2c..903b8c434a1 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -416,6 +416,7 @@ const std::unordered_map<std::string, BSONType> TypeMatchExpression::typeAliasMa {"int", NumberInt}, {"timestamp", bsonTimestamp}, {"long", NumberLong}, + {"decimal", NumberDecimal}, {"maxKey", MaxKey}, {"minKey", MinKey}}; diff --git a/src/mongo/db/matcher/expression_parser_leaf_test.cpp b/src/mongo/db/matcher/expression_parser_leaf_test.cpp index cb1cd8ff375..c5650c25d67 100644 --- a/src/mongo/db/matcher/expression_parser_leaf_test.cpp +++ b/src/mongo/db/matcher/expression_parser_leaf_test.cpp @@ -38,6 +38,7 @@ #include "mongo/db/json.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_leaf.h" +#include "mongo/platform/decimal128.h" #include "mongo/util/log.h" namespace mongo { @@ -608,6 +609,17 @@ TEST(MatchExpressionParserLeafTest, TypeDoubleOperator) { ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); } +TEST(MatchExpressionParserLeafTest, TypeDecimalOperator) { + if (Decimal128::enabled) { + BSONObj query = BSON("x" << BSON("$type" << mongo::NumberDecimal)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT_FALSE(result.getValue()->matchesBSON(BSON("x" << 5.3))); + ASSERT_TRUE(result.getValue()->matchesBSON(BSON("x" << mongo::Decimal128("1")))); + } +} + TEST(MatchExpressionParserLeafTest, TypeNull) { BSONObj query = BSON("x" << BSON("$type" << jstNULL)); StatusWithMatchExpression result = MatchExpressionParser::parse(query); @@ -657,6 +669,19 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameDouble) { ASSERT_FALSE(tmeNumberDouble->matchesBSON(fromjson("{a: NumberInt(5)}"))); } +TEST(MatchExpressionParserLeafTest, TypeStringNameNumberDecimal) { + if (Decimal128::enabled) { + StatusWithMatchExpression typeNumberDecimal = + MatchExpressionParser::parse(fromjson("{a: {$type: 'decimal'}}")); + ASSERT_OK(typeNumberDecimal.getStatus()); + TypeMatchExpression* tmeNumberDecimal = + static_cast<TypeMatchExpression*>(typeNumberDecimal.getValue().get()); + ASSERT(tmeNumberDecimal->getType() == NumberDecimal); + ASSERT_TRUE(tmeNumberDecimal->matchesBSON(BSON("a" << mongo::Decimal128("1")))); + ASSERT_FALSE(tmeNumberDecimal->matchesBSON(fromjson("{a: true}"))); + } +} + TEST(MatchExpressionParserLeafTest, TypeStringnameNumberInt) { StatusWithMatchExpression typeNumberInt = MatchExpressionParser::parse(fromjson("{a: {$type: 'int'}}")); diff --git a/src/mongo/db/ops/modifier_bit_test.cpp b/src/mongo/db/ops/modifier_bit_test.cpp index b6a4fb38a7b..2e63dbe4953 100644 --- a/src/mongo/db/ops/modifier_bit_test.cpp +++ b/src/mongo/db/ops/modifier_bit_test.cpp @@ -37,11 +37,13 @@ #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/ops/log_builder.h" +#include "mongo/platform/decimal128.h" #include "mongo/unittest/unittest.h" namespace { using mongo::BSONObj; +using mongo::Decimal128; using mongo::LogBuilder; using mongo::ModifierBit; using mongo::ModifierInterface; @@ -116,6 +118,13 @@ TEST(Init, FailToInitWithInvalidValue) { modObj = fromjson("{ $bit : { a : { or : 1.0 } } }"); ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(), ModifierInterface::Options::normal())); + + if (mongo::Decimal128::enabled) { + // The argument to the sub-operator must be integral + modObj = fromjson("{ $bit : { a : { or : NumberDecimal(\"1.0\") } } }"); + ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(), + ModifierInterface::Options::normal())); + } } TEST(Init, ParsesAndInt) { diff --git a/src/mongo/db/ops/modifier_inc_test.cpp b/src/mongo/db/ops/modifier_inc_test.cpp index 6a0e3a332bd..52c829f7842 100644 --- a/src/mongo/db/ops/modifier_inc_test.cpp +++ b/src/mongo/db/ops/modifier_inc_test.cpp @@ -39,11 +39,13 @@ #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/ops/log_builder.h" +#include "mongo/platform/decimal128.h" #include "mongo/unittest/unittest.h" namespace { using mongo::BSONObj; +using mongo::Decimal128; using mongo::LogBuilder; using mongo::ModifierInc; using mongo::ModifierInterface; @@ -122,6 +124,12 @@ TEST(Init, InitParsesNumberDouble) { Mod incMod(BSON("$inc" << BSON("a" << 1.0))); } +TEST(Init, InitParsesNumberDecimal) { + if (mongo::Decimal128::enabled) { + Mod incMod(BSON("$inc" << BSON("a" << Decimal128(1.0)))); + } +} + TEST(SimpleMod, PrepareSimpleOK) { Document doc(fromjson("{ a : 1 }")); Mod incMod(fromjson("{ $inc: { a : 1 }}")); @@ -275,6 +283,16 @@ TEST(NoOp, Double) { ASSERT_TRUE(execInfo.noOp); } +TEST(NoOp, Decimal) { + if (mongo::Decimal128::enabled) { + Document doc(BSON("a" << Decimal128("1.0"))); + Mod incMod(BSON("$inc" << BSON("a" << Decimal128("0.0")))); + ModifierInterface::ExecInfo execInfo; + ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo)); + ASSERT_TRUE(execInfo.noOp); + } +} + TEST(Upcasting, UpcastIntToLong) { // Checks that $inc : NumberLong(0) turns a NumberInt into a NumberLong and logs it // correctly. @@ -370,9 +388,112 @@ TEST(Upcasting, DoublesStayDoubles) { ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType()); } +TEST(Upcasting, UpcastIntToDecimal) { + if (mongo::Decimal128::enabled) { + // Checks that $inc : NumberDecimal(0) turns a NumberInt into a NumberDecimal and logs it + // correctly. + Document doc(BSON("a" << static_cast<int>(1))); + ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType()); + + Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"0\") }}")); + + ModifierInterface::ExecInfo execInfo; + ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo)); + ASSERT_FALSE(execInfo.noOp); + + ASSERT_OK(incMod.apply()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc); + ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType()); + + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + ASSERT_OK(incMod.log(&logBuilder)); + ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc); + ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType()); + } +} + +TEST(Upcasting, UpcastLongToDecimal) { + if (mongo::Decimal128::enabled) { + // Checks that $inc : NumberDecimal(0) turns a NumberLong into a NumberDecimal and logs it + // correctly. + Document doc(BSON("a" << static_cast<long long>(1))); + ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType()); + + Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"0\") }}")); + + ModifierInterface::ExecInfo execInfo; + ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo)); + ASSERT_FALSE(execInfo.noOp); + + ASSERT_OK(incMod.apply()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc); + ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType()); + + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + ASSERT_OK(incMod.log(&logBuilder)); + ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc); + ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType()); + } +} + +TEST(Upcasting, UpcastDoubleToDecimal) { + if (mongo::Decimal128::enabled) { + // Checks that $inc : NumberDecimal(0) turns a double into a NumberDecimal and logs it + // correctly. + Document doc(BSON("a" << static_cast<double>(1.0))); + ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType()); + + Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"0\") }}")); + + ModifierInterface::ExecInfo execInfo; + ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo)); + ASSERT_FALSE(execInfo.noOp); + + ASSERT_OK(incMod.apply()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc); + ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType()); + + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + ASSERT_OK(incMod.log(&logBuilder)); + ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc); + ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType()); + } +} + +TEST(Upcasting, DecimalsStayDecimals) { + if (mongo::Decimal128::enabled) { + // Checks that $inc : NumberDecimal(1) keeps a NumberDecimal as a NumberDecimal and logs it + // correctly. + Document doc(BSON("a" << mongo::Decimal128("1.0"))); + ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType()); + + Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"1\") }}")); + + ModifierInterface::ExecInfo execInfo; + ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo)); + ASSERT_FALSE(execInfo.noOp); + + ASSERT_OK(incMod.apply()); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"2.0\") }"), doc); + ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType()); + + Document logDoc; + LogBuilder logBuilder(logDoc.root()); + ASSERT_OK(incMod.log(&logBuilder)); + ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"2.0\") }}"), logDoc); + ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType()); + } +} + // The only interesting overflow cases are int->long via increment: we never overflow to // double, and we never decrease precision on decrement. - TEST(Spilling, OverflowIntToLong) { const int initial_value = std::numeric_limits<int32_t>::max(); diff --git a/src/mongo/platform/decimal128.h b/src/mongo/platform/decimal128.h index 7a1188a8904..cc6245f93af 100644 --- a/src/mongo/platform/decimal128.h +++ b/src/mongo/platform/decimal128.h @@ -306,6 +306,4 @@ private: Value _value; }; -std::ostream& operator<<(std::ostream& stream, const Decimal128& value); - } // namespace mongo diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp index b2ee26f9fcc..e4463106ece 100644 --- a/src/mongo/scripting/engine_v8.cpp +++ b/src/mongo/scripting/engine_v8.cpp @@ -1321,7 +1321,7 @@ void V8Scope::installBSONTypes() { injectV8Function("BinData", BinDataFT(), _global); injectV8Function("NumberLong", NumberLongFT(), _global); injectV8Function("NumberInt", NumberIntFT(), _global); - if (experimentalDecimalSupport) { + if (Decimal128::enabled) { injectV8Function("NumberDecimal", NumberDecimalFT(), _global); } injectV8Function("Timestamp", TimestampFT(), _global); diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index e71137d005a..23b7d4c05fb 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -669,7 +669,7 @@ void MozJSImplScope::installBSONTypes() { _nativeFunctionProto.install(_global); _numberIntProto.install(_global); _numberLongProto.install(_global); - if (experimentalDecimalSupport) { + if (Decimal128::enabled) { _numberDecimalProto.install(_global); } _objectProto.install(_global); 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) { |