From 8b4c999888ad7929a7f1c82d8fdb067fa50cf6bb Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 12 Feb 2016 16:33:31 +0800 Subject: [PATCH] Add Value::GetFloat(), Value::IsLossLessFloat/Double() Fix #341 --- include/rapidjson/document.h | 34 +++++++++++++++++++++++++ test/unittest/valuetest.cpp | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 2cd9088..b0a5d34 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -788,6 +788,29 @@ public: bool IsDouble() const { return (flags_ & kDoubleFlag) != 0; } bool IsString() const { return (flags_ & kStringFlag) != 0; } + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) return static_cast(static_cast(GetUint64())) == GetUint64(); + if (IsInt64()) return static_cast< int64_t>(static_cast(GetInt64())) == GetInt64(); + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((flags_ & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + //@} //!@name Null @@ -1445,6 +1468,9 @@ public: int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; } uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; } + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ double GetDouble() const { RAPIDJSON_ASSERT(IsNumber()); if ((flags_ & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. @@ -1454,11 +1480,19 @@ public: RAPIDJSON_ASSERT((flags_ & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) } + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + double GetFloat() const { + return static_cast(GetDouble()); + } + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(f); return *this; } //@} diff --git a/test/unittest/valuetest.cpp b/test/unittest/valuetest.cpp index 377554d..4179ccb 100644 --- a/test/unittest/valuetest.cpp +++ b/test/unittest/valuetest.cpp @@ -579,6 +579,54 @@ TEST(Value, Double) { EXPECT_NEAR(56.78, z.GetDouble(), 0.0); } +TEST(Value, Float) { + // Constructor with double + Value x(12.34f); + EXPECT_EQ(kNumberType, x.GetType()); + EXPECT_NEAR(12.34f, x.GetFloat(), 0.0); + EXPECT_TRUE(x.IsNumber()); + EXPECT_TRUE(x.IsDouble()); + EXPECT_TRUE(x.IsFloat()); + + EXPECT_FALSE(x.IsInt()); + EXPECT_FALSE(x.IsNull()); + EXPECT_FALSE(x.IsBool()); + EXPECT_FALSE(x.IsFalse()); + EXPECT_FALSE(x.IsTrue()); + EXPECT_FALSE(x.IsString()); + EXPECT_FALSE(x.IsObject()); + EXPECT_FALSE(x.IsArray()); + + // SetFloat() + Value z; + z.SetFloat(12.34f); + EXPECT_NEAR(12.34f, z.GetFloat(), 0.0f); + + z = 56.78f; + EXPECT_NEAR(56.78f, z.GetFloat(), 0.0f); +} + +TEST(Value, IsLosslessDouble) { + EXPECT_TRUE(Value(12.34).IsLosslessDouble()); + EXPECT_TRUE(Value(-123).IsLosslessDouble()); + EXPECT_TRUE(Value(2147483648u).IsLosslessDouble()); + EXPECT_TRUE(Value(static_cast(-RAPIDJSON_UINT64_C2(0x40000000, 0x00000000))).IsLosslessDouble()); + EXPECT_TRUE(Value(RAPIDJSON_UINT64_C2(0xA0000000, 0x00000000)).IsLosslessDouble()); + + EXPECT_FALSE(Value(static_cast(-RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble()); + EXPECT_FALSE(Value(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)).IsLosslessDouble()); +} + +TEST(Value, IsLosslessFloat) { + EXPECT_TRUE(Value(12.25).IsLosslessFloat()); + EXPECT_TRUE(Value(-123).IsLosslessFloat()); + EXPECT_TRUE(Value(2147483648u).IsLosslessFloat()); + EXPECT_TRUE(Value(3.4028234e38f).IsLosslessFloat()); + EXPECT_TRUE(Value(-3.4028234e38f).IsLosslessFloat()); + EXPECT_FALSE(Value(3.4028235e38).IsLosslessFloat()); + EXPECT_FALSE(Value(0.3).IsLosslessFloat()); +} + TEST(Value, String) { // Construction with const string Value x("Hello", 5); // literal