diff --git a/extras/tests/Numbers/CMakeLists.txt b/extras/tests/Numbers/CMakeLists.txt index 50ab5135..30c123cd 100644 --- a/extras/tests/Numbers/CMakeLists.txt +++ b/extras/tests/Numbers/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(NumbersTests parseFloat.cpp + parseDouble.cpp parseInteger.cpp parseNumber.cpp ) diff --git a/extras/tests/Numbers/parseDouble.cpp b/extras/tests/Numbers/parseDouble.cpp new file mode 100644 index 00000000..da57357b --- /dev/null +++ b/extras/tests/Numbers/parseDouble.cpp @@ -0,0 +1,97 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2020 +// MIT License + +#define ARDUINOJSON_USE_DOUBLE 1 +#define ARDUINOJSON_ENABLE_NAN 1 +#define ARDUINOJSON_ENABLE_INFINITY 1 + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +void checkDouble(const char* input, double expected) { + CAPTURE(input); + REQUIRE(parseNumber(input) == Approx(expected)); +} + +void checkDoubleNaN(const char* input) { + CAPTURE(input); + double result = parseNumber(input); + REQUIRE(result != result); +} + +void checkDoubleInf(const char* input, bool negative) { + CAPTURE(input); + double x = parseNumber(input); + if (negative) + REQUIRE(x < 0); + else + REQUIRE(x > 0); + REQUIRE(x == x); // not a NaN + REQUIRE(x * 2 == x); // a property of infinity +} + +TEST_CASE("parseNumber()") { + SECTION("Short_NoExponent") { + checkDouble("3.14", 3.14); + checkDouble("-3.14", -3.14); + checkDouble("+3.14", +3.14); + } + + SECTION("Short_NoDot") { + checkDouble("1E+308", 1E+308); + checkDouble("-1E+308", -1E+308); + checkDouble("+1E-308", +1E-308); + checkDouble("+1e+308", +1e+308); + checkDouble("-1e-308", -1e-308); + } + + SECTION("Max") { + checkDouble(".017976931348623147e+310", 1.7976931348623147e+308); + checkDouble(".17976931348623147e+309", 1.7976931348623147e+308); + checkDouble("1.7976931348623147e+308", 1.7976931348623147e+308); + checkDouble("17.976931348623147e+307", 1.7976931348623147e+308); + checkDouble("179.76931348623147e+306", 1.7976931348623147e+308); + } + + SECTION("Min") { + checkDouble(".022250738585072014e-306", 2.2250738585072014e-308); + checkDouble(".22250738585072014e-307", 2.2250738585072014e-308); + checkDouble("2.2250738585072014e-308", 2.2250738585072014e-308); + checkDouble("22.250738585072014e-309", 2.2250738585072014e-308); + checkDouble("222.50738585072014e-310", 2.2250738585072014e-308); + } + + SECTION("VeryLong") { + checkDouble("0.00000000000000000000000000000001", 1e-32); + checkDouble("100000000000000000000000000000000.0", 1e+32); + checkDouble( + "100000000000000000000000000000000.00000000000000000000000000000", + 1e+32); + } + + SECTION("MantissaTooLongToFit") { + checkDouble("0.179769313486231571111111111111", 0.17976931348623157); + checkDouble("17976931348623157.11111111111111", 17976931348623157.0); + checkDouble("1797693.134862315711111111111111", 1797693.1348623157); + + checkDouble("-0.179769313486231571111111111111", -0.17976931348623157); + checkDouble("-17976931348623157.11111111111111", -17976931348623157.0); + checkDouble("-1797693.134862315711111111111111", -1797693.1348623157); + } + + SECTION("ExponentTooBig") { + checkDoubleInf("1e309", false); + checkDoubleInf("-1e309", true); + checkDoubleInf("1e65535", false); + checkDouble("1e-65535", 0.0); + } + + SECTION("NaN") { + checkDoubleNaN("NaN"); + checkDoubleNaN("nan"); + } +} diff --git a/extras/tests/Numbers/parseFloat.cpp b/extras/tests/Numbers/parseFloat.cpp index f79d6976..84085fe8 100644 --- a/extras/tests/Numbers/parseFloat.cpp +++ b/extras/tests/Numbers/parseFloat.cpp @@ -6,28 +6,26 @@ #define ARDUINOJSON_ENABLE_NAN 1 #define ARDUINOJSON_ENABLE_INFINITY 1 -#include +#include +#include #include using namespace ARDUINOJSON_NAMESPACE; -template -void checkFloat(const char* input, T expected) { +void checkFloat(const char* input, float expected) { CAPTURE(input); - REQUIRE(parseFloat(input) == Approx(expected)); + REQUIRE(parseNumber(input) == Approx(expected)); } -template -void checkNaN(const char* input) { +void checkFloatNaN(const char* input) { CAPTURE(input); - T result = parseFloat(input); + float result = parseNumber(input); REQUIRE(result != result); } -template -void checkInf(const char* input, bool negative) { +void checkFloatInf(const char* input, bool negative) { CAPTURE(input); - T x = parseFloat(input); + float x = parseNumber(input); if (negative) REQUIRE(x < 0); else @@ -36,137 +34,69 @@ void checkInf(const char* input, bool negative) { REQUIRE(x * 2 == x); // a property of infinity } -TEST_CASE("parseFloat()") { +TEST_CASE("parseNumber()") { SECTION("Float_Short_NoExponent") { - checkFloat("3.14", 3.14f); - checkFloat("-3.14", -3.14f); - checkFloat("+3.14", +3.14f); + checkFloat("3.14", 3.14f); + checkFloat("-3.14", -3.14f); + checkFloat("+3.14", +3.14f); } SECTION("Short_NoDot") { - checkFloat("1E+38", 1E+38f); - checkFloat("-1E+38", -1E+38f); - checkFloat("+1E-38", +1E-38f); - checkFloat("+1e+38", +1e+38f); - checkFloat("-1e-38", -1e-38f); + checkFloat("1E+38", 1E+38f); + checkFloat("-1E+38", -1E+38f); + checkFloat("+1E-38", +1E-38f); + checkFloat("+1e+38", +1e+38f); + checkFloat("-1e-38", -1e-38f); } SECTION("Max") { - checkFloat("340.2823e+36", 3.402823e+38f); - checkFloat("34.02823e+37", 3.402823e+38f); - checkFloat("3.402823e+38", 3.402823e+38f); - checkFloat("0.3402823e+39", 3.402823e+38f); - checkFloat("0.03402823e+40", 3.402823e+38f); - checkFloat("0.003402823e+41", 3.402823e+38f); + checkFloat("340.2823e+36", 3.402823e+38f); + checkFloat("34.02823e+37", 3.402823e+38f); + checkFloat("3.402823e+38", 3.402823e+38f); + checkFloat("0.3402823e+39", 3.402823e+38f); + checkFloat("0.03402823e+40", 3.402823e+38f); + checkFloat("0.003402823e+41", 3.402823e+38f); } SECTION("VeryLong") { - checkFloat("0.00000000000000000000000000000001", 1e-32f); - checkFloat("100000000000000000000000000000000.0", 1e+32f); - checkFloat( + checkFloat("0.00000000000000000000000000000001", 1e-32f); + checkFloat("100000000000000000000000000000000.0", 1e+32f); + checkFloat( "100000000000000000000000000000000.00000000000000000000000000000", 1e+32f); } SECTION("MantissaTooLongToFit") { - checkFloat("0.340282346638528861111111111111", 0.34028234663852886f); - checkFloat("34028234663852886.11111111111111", 34028234663852886.0f); - checkFloat("34028234.66385288611111111111111", 34028234.663852886f); + checkFloat("0.340282346638528861111111111111", 0.34028234663852886f); + checkFloat("34028234663852886.11111111111111", 34028234663852886.0f); + checkFloat("34028234.66385288611111111111111", 34028234.663852886f); - checkFloat("-0.340282346638528861111111111111", - -0.34028234663852886f); - checkFloat("-34028234663852886.11111111111111", - -34028234663852886.0f); - checkFloat("-34028234.66385288611111111111111", - -34028234.663852886f); + checkFloat("-0.340282346638528861111111111111", -0.34028234663852886f); + checkFloat("-34028234663852886.11111111111111", -34028234663852886.0f); + checkFloat("-34028234.66385288611111111111111", -34028234.663852886f); } SECTION("ExponentTooBig") { - checkInf("1e39", false); - checkInf("-1e39", true); - checkInf("1e255", false); - checkFloat("1e-255", 0.0f); + checkFloatInf("1e39", false); + checkFloatInf("-1e39", true); + checkFloatInf("1e255", false); + checkFloat("1e-255", 0.0f); } SECTION("NaN") { - checkNaN("NaN"); - checkNaN("nan"); + checkFloatNaN("NaN"); + checkFloatNaN("nan"); } SECTION("Infinity") { - checkInf("Infinity", false); - checkInf("+Infinity", false); - checkInf("-Infinity", true); - checkInf("inf", false); - checkInf("+inf", false); - checkInf("-inf", true); + checkFloatInf("Infinity", false); + checkFloatInf("+Infinity", false); + checkFloatInf("-Infinity", true); + checkFloatInf("inf", false); + checkFloatInf("+inf", false); + checkFloatInf("-inf", true); - checkInf("1e300", false); - checkInf("-1e300", true); - } -} - -TEST_CASE("parseFloat()") { - SECTION("Short_NoExponent") { - checkFloat("3.14", 3.14); - checkFloat("-3.14", -3.14); - checkFloat("+3.14", +3.14); - } - - SECTION("Short_NoDot") { - checkFloat("1E+308", 1E+308); - checkFloat("-1E+308", -1E+308); - checkFloat("+1E-308", +1E-308); - checkFloat("+1e+308", +1e+308); - checkFloat("-1e-308", -1e-308); - } - - SECTION("Max") { - checkFloat(".017976931348623147e+310", 1.7976931348623147e+308); - checkFloat(".17976931348623147e+309", 1.7976931348623147e+308); - checkFloat("1.7976931348623147e+308", 1.7976931348623147e+308); - checkFloat("17.976931348623147e+307", 1.7976931348623147e+308); - checkFloat("179.76931348623147e+306", 1.7976931348623147e+308); - } - - SECTION("Min") { - checkFloat(".022250738585072014e-306", 2.2250738585072014e-308); - checkFloat(".22250738585072014e-307", 2.2250738585072014e-308); - checkFloat("2.2250738585072014e-308", 2.2250738585072014e-308); - checkFloat("22.250738585072014e-309", 2.2250738585072014e-308); - checkFloat("222.50738585072014e-310", 2.2250738585072014e-308); - } - - SECTION("VeryLong") { - checkFloat("0.00000000000000000000000000000001", 1e-32); - checkFloat("100000000000000000000000000000000.0", 1e+32); - checkFloat( - "100000000000000000000000000000000.00000000000000000000000000000", - 1e+32); - } - - SECTION("MantissaTooLongToFit") { - checkFloat("0.179769313486231571111111111111", 0.17976931348623157); - checkFloat("17976931348623157.11111111111111", 17976931348623157.0); - checkFloat("1797693.134862315711111111111111", 1797693.1348623157); - - checkFloat("-0.179769313486231571111111111111", - -0.17976931348623157); - checkFloat("-17976931348623157.11111111111111", - -17976931348623157.0); - checkFloat("-1797693.134862315711111111111111", - -1797693.1348623157); - } - - SECTION("ExponentTooBig") { - checkInf("1e309", false); - checkInf("-1e309", true); - checkInf("1e65535", false); - checkFloat("1e-65535", 0.0); - } - - SECTION("NaN") { - checkNaN("NaN"); - checkNaN("nan"); + checkFloatInf("1e300", false); + checkFloatInf("-1e300", true); } } diff --git a/extras/tests/Numbers/parseInteger.cpp b/extras/tests/Numbers/parseInteger.cpp index 44f4a182..745356cb 100644 --- a/extras/tests/Numbers/parseInteger.cpp +++ b/extras/tests/Numbers/parseInteger.cpp @@ -3,7 +3,8 @@ // MIT License #include -#include +#include +#include #include using namespace ARDUINOJSON_NAMESPACE; @@ -11,11 +12,11 @@ using namespace ARDUINOJSON_NAMESPACE; template void checkInteger(const char* input, T expected) { CAPTURE(input); - T actual = parseInteger(input); + T actual = parseNumber(input); REQUIRE(expected == actual); } -TEST_CASE("parseInteger()") { +TEST_CASE("parseNumber()") { checkInteger("-128", -128); checkInteger("127", 127); checkInteger("+127", 127); @@ -25,7 +26,7 @@ TEST_CASE("parseInteger()") { checkInteger("-129", 0); // overflow } -TEST_CASE("parseInteger()") { +TEST_CASE("parseNumber()") { checkInteger("-32768", -32768); checkInteger("32767", 32767); checkInteger("+32767", 32767); @@ -35,7 +36,7 @@ TEST_CASE("parseInteger()") { checkInteger("32768", 0); // overflow } -TEST_CASE("parseInteger()") { +TEST_CASE("parseNumber()") { checkInteger("-2147483648", (-2147483647 - 1)); checkInteger("2147483647", 2147483647); checkInteger("+2147483647", 2147483647); @@ -45,7 +46,7 @@ TEST_CASE("parseInteger()") { checkInteger("2147483648", 0); // overflow } -TEST_CASE("parseInteger()") { +TEST_CASE("parseNumber()") { checkInteger("0", 0); checkInteger("255", 255); checkInteger("+255", 255); @@ -55,7 +56,7 @@ TEST_CASE("parseInteger()") { checkInteger("256", 0); } -TEST_CASE("parseInteger()") { +TEST_CASE("parseNumber()") { checkInteger("0", 0); checkInteger("65535", 65535); checkInteger("+65535", 65535); diff --git a/extras/tests/Numbers/parseNumber.cpp b/extras/tests/Numbers/parseNumber.cpp index 38eafc99..9e8a2cb7 100644 --- a/extras/tests/Numbers/parseNumber.cpp +++ b/extras/tests/Numbers/parseNumber.cpp @@ -2,22 +2,37 @@ // Copyright Benoit Blanchon 2014-2020 // MIT License +#include #include +#include #include using namespace ARDUINOJSON_NAMESPACE; -TEST_CASE("Test uint32_t overflow") { - ParsedNumber first, second; - parseNumber("4294967295", first); - parseNumber("4294967296", second); +TEST_CASE("Test unsigned integer overflow") { + VariantData first, second; + first.init(); + second.init(); + + // Avoids MSVC warning C4127 (conditional expression is constant) + size_t integerSize = sizeof(Integer); + + if (integerSize == 8) { + parseNumber("18446744073709551615", first); + parseNumber("18446744073709551616", second); + } else { + parseNumber("4294967295", first); + parseNumber("4294967296", second); + } REQUIRE(first.type() == uint8_t(VALUE_IS_POSITIVE_INTEGER)); REQUIRE(second.type() == uint8_t(VALUE_IS_FLOAT)); } TEST_CASE("Invalid value") { - ParsedNumber result; + VariantData result; + result.init(); + parseNumber("6a3", result); REQUIRE(result.type() == uint8_t(VALUE_IS_NULL)); diff --git a/src/ArduinoJson/Json/JsonDeserializer.hpp b/src/ArduinoJson/Json/JsonDeserializer.hpp index 16094a08..0f825509 100644 --- a/src/ArduinoJson/Json/JsonDeserializer.hpp +++ b/src/ArduinoJson/Json/JsonDeserializer.hpp @@ -499,26 +499,12 @@ class JsonDeserializer { return true; } - ParsedNumber num; - parseNumber(_buffer, num); - - switch (num.type()) { - case VALUE_IS_NEGATIVE_INTEGER: - result.setNegativeInteger(num.uintValue); - return true; - - case VALUE_IS_POSITIVE_INTEGER: - result.setPositiveInteger(num.uintValue); - return true; - - case VALUE_IS_FLOAT: - result.setFloat(num.floatValue); - return true; - - default: - _error = DeserializationError::InvalidInput; - return false; + if (!parseNumber(_buffer, result)) { + _error = DeserializationError::InvalidInput; + return false; } + + return true; } bool skipNumericValue() { diff --git a/src/ArduinoJson/Numbers/parseFloat.hpp b/src/ArduinoJson/Numbers/parseFloat.hpp deleted file mode 100644 index 40c65c71..00000000 --- a/src/ArduinoJson/Numbers/parseFloat.hpp +++ /dev/null @@ -1,20 +0,0 @@ -// ArduinoJson - arduinojson.org -// Copyright Benoit Blanchon 2014-2020 -// MIT License - -#pragma once - -#include -#include - -namespace ARDUINOJSON_NAMESPACE { - -template -inline T parseFloat(const char* s) { - // try to reuse the same parameters as JsonDeserializer - typedef typename choose_largest::type TFloat; - ParsedNumber value; - parseNumber(s, value); - return value.template as(); -} -} // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Numbers/parseInteger.hpp b/src/ArduinoJson/Numbers/parseInteger.hpp deleted file mode 100644 index d8a54ea4..00000000 --- a/src/ArduinoJson/Numbers/parseInteger.hpp +++ /dev/null @@ -1,21 +0,0 @@ -// ArduinoJson - arduinojson.org -// Copyright Benoit Blanchon 2014-2020 -// MIT License - -#pragma once - -#include -#include -#include - -namespace ARDUINOJSON_NAMESPACE { -template -T parseInteger(const char *s) { - // try to reuse the same parameters as JsonDeserializer - typedef typename choose_largest::type>::type - TUInt; - ParsedNumber value; - parseNumber(s, value); - return value.template as(); -} -} // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Numbers/parseNumber.hpp b/src/ArduinoJson/Numbers/parseNumber.hpp index 76f28ea4..2bd18227 100644 --- a/src/ArduinoJson/Numbers/parseNumber.hpp +++ b/src/ArduinoJson/Numbers/parseNumber.hpp @@ -10,59 +10,18 @@ #include #include #include -#include +#include +#include namespace ARDUINOJSON_NAMESPACE { -template -struct ParsedNumber { - ParsedNumber() : _type(VALUE_IS_NULL) {} - - void setInteger(TUInt value, bool is_negative) { - uintValue = value; - _type = uint8_t(is_negative ? VALUE_IS_NEGATIVE_INTEGER - : VALUE_IS_POSITIVE_INTEGER); - } - - void setFloat(TFloat value) { - floatValue = value; - _type = VALUE_IS_FLOAT; - } - - template - T as() const { - switch (_type) { - case VALUE_IS_NEGATIVE_INTEGER: - return convertNegativeInteger(uintValue); - case VALUE_IS_POSITIVE_INTEGER: - return convertPositiveInteger(uintValue); - case VALUE_IS_FLOAT: - return convertFloat(floatValue); - default: - return 0; - } - } - - uint8_t type() const { - return _type; - } - - union { - TUInt uintValue; - TFloat floatValue; - }; - uint8_t _type; -}; // namespace ARDUINOJSON_NAMESPACE - template struct choose_largest : conditional<(sizeof(A) > sizeof(B)), A, B> {}; -template -inline void parseNumber(const char* s, ParsedNumber& result) { - typedef FloatTraits traits; - typedef typename choose_largest::type - mantissa_t; - typedef typename traits::exponent_type exponent_t; +inline bool parseNumber(const char* s, VariantData& result) { + typedef FloatTraits traits; + typedef choose_largest::type mantissa_t; + typedef traits::exponent_type exponent_t; ARDUINOJSON_ASSERT(s != 0); @@ -80,24 +39,23 @@ inline void parseNumber(const char* s, ParsedNumber& result) { #if ARDUINOJSON_ENABLE_NAN if (*s == 'n' || *s == 'N') { result.setFloat(traits::nan()); - return; + return true; } - #endif #if ARDUINOJSON_ENABLE_INFINITY if (*s == 'i' || *s == 'I') { result.setFloat(is_negative ? -traits::inf() : traits::inf()); - return; + return true; } #endif if (!isdigit(*s) && *s != '.') - return; + return false; mantissa_t mantissa = 0; exponent_t exponent_offset = 0; - const mantissa_t maxUint = TUInt(-1); + const mantissa_t maxUint = UInt(-1); while (isdigit(*s)) { uint8_t digit = uint8_t(*s - '0'); @@ -111,8 +69,11 @@ inline void parseNumber(const char* s, ParsedNumber& result) { } if (*s == '\0') { - result.setInteger(TUInt(mantissa), is_negative); - return; + if (is_negative) + result.setNegativeInteger(UInt(mantissa)); + else + result.setPositiveInteger(UInt(mantissa)); + return true; } // avoid mantissa overflow @@ -156,7 +117,7 @@ inline void parseNumber(const char* s, ParsedNumber& result) { result.setFloat(is_negative ? -0.0f : 0.0f); else result.setFloat(is_negative ? -traits::inf() : traits::inf()); - return; + return true; } s++; } @@ -167,11 +128,20 @@ inline void parseNumber(const char* s, ParsedNumber& result) { // we should be at the end of the string, otherwise it's an error if (*s != '\0') - return; + return false; - TFloat final_result = - traits::make_float(static_cast(mantissa), exponent); + Float final_result = + traits::make_float(static_cast(mantissa), exponent); result.setFloat(is_negative ? -final_result : final_result); + return true; +} + +template +inline T parseNumber(const char* s) { + VariantData value; + value.init(); // VariantData is a POD, so it has no constructor + parseNumber(s, value); + return variantAs(&value); } } // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index f2c0e0f7..1c10c5f4 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -33,6 +33,9 @@ class VariantData { // - no destructor // - no virtual // - no inheritance + void init() { + _flags = 0; + } template typename TVisitor::result_type accept(TVisitor &visitor) const { @@ -365,11 +368,11 @@ class VariantData { _content.asCollection.movePointers(stringDistance, variantDistance); } - private: uint8_t type() const { return _flags & VALUE_MASK; } + private: void setType(uint8_t t) { _flags &= KEY_IS_OWNED; _flags |= t; diff --git a/src/ArduinoJson/Variant/VariantImpl.hpp b/src/ArduinoJson/Variant/VariantImpl.hpp index 49af574e..cd347d1d 100644 --- a/src/ArduinoJson/Variant/VariantImpl.hpp +++ b/src/ArduinoJson/Variant/VariantImpl.hpp @@ -4,10 +4,11 @@ #pragma once +#include #include #include -#include -#include +#include +#include #include #include // for strcmp @@ -24,7 +25,7 @@ inline T VariantData::asIntegral() const { return convertNegativeInteger(_content.asInteger); case VALUE_IS_LINKED_STRING: case VALUE_IS_OWNED_STRING: - return parseInteger(_content.asString); + return parseNumber(_content.asString); case VALUE_IS_FLOAT: return convertFloat(_content.asFloat); default: @@ -58,7 +59,7 @@ inline T VariantData::asFloat() const { return -static_cast(_content.asInteger); case VALUE_IS_LINKED_STRING: case VALUE_IS_OWNED_STRING: - return parseFloat(_content.asString); + return parseNumber(_content.asString); case VALUE_IS_FLOAT: return static_cast(_content.asFloat); default: